Category Archives: Roslyn

Roslyn: Analiza przepływu kontroli

W poprzednim wpisie zajęliśmy się przepływem danych, czyli sprawdzaliśmy jakie zmienne zostały zapisane lub odczytane. Analogiczną analizą jest control flow. Dzięki niej, wiemy  w którym momencie funkcja może zakończyć działanie np. przez instrukcje “return”.

Tak jak wcześniej, najpierw musimy skompilować kod:

SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public string TestMethod1(string a) { int localA=3,localB=5; for(int i=0;i<=10;i++) { if(a[i]!='') break; } localB++; return a; } } "); var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var compilation = CSharpCompilation.Create("TestAssembly", new[] { tree }, new[] { mscorlib });

Kolejny krok to uzyskanie SemanticsModel:

SemanticModel model = compilation.GetSemanticModel(tree,true);

Analiza przypływu kontroli sprowadza się do:

ControlFlowAnalysis controlFlow = model.AnalyzeControlFlow(node);

Zajrzyjmy, co za pomocą ControlFlowAnalysis możemy dowiedzieć się:

public abstract class ControlFlowAnalysis { public abstract ImmutableArray<SyntaxNode> EntryPoints { get; } public abstract ImmutableArray<SyntaxNode> ExitPoints { get; } public abstract bool EndPointIsReachable { get; } public abstract bool StartPointIsReachable { get; } public abstract ImmutableArray<SyntaxNode> ReturnStatements { get; } public abstract bool Succeeded { get; } }

EntryPoints oraz ExitPoitns opisują wejścia do gałęzi kodu oraz ich wyjścia. ReturnStatements, jak nie trudno domyślić się, zawiera informacje o instrukcjach return, które zawsze kończą działanie funkcji.

Roslyn: Analiza przepływu danych–semantyczny model

Semantyczny model dostarcza nam wiele informacji o kodzie, które zwykle uzyskuje się po kompilacji. Na przykład przeładowanie metod jest dużo łatwiejsze do określenia już po kompilacji.  Oznacza to, że nie jest już to  klasyczna, statyczna analiza kodu. Z tego względu, najpierw danych kod należy skompilować za pomocą:

var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); var compilation = CSharpCompilation.Create("TestAssembly", new[] { tree }, new[] { mscorlib }); SemanticModel model = compilation.GetSemanticModel(tree);

Posiadając semantyczny model, możemy dokonać tzw. analizy przepływu danych. Jak sama nazwa wskazuje, dzięki niej będziemy wiedzieli, co dzieje się z naszymi danymi, a w szczególności:

  1. Jakie zmienne są odczytywane
  2. Jakie zmienne są zapisywane
  3. Jakie zmienne są deklarowane
  4. Co dzieje się ze zmiennymi w wewnątrz i na zewnątrz bloku

Jak widać, w celu uzyskania modelu, wystarczy wywołać GetSemanticModel na kompilacji.  Ponadto załóżmy, ze powyższy SyntacTree zawiera następujący kod:

SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public void TestMethod1(string a) { int localA=3,localB=5; if(a!=null) { localA++; } localB++; } } ");

Następnie, w celu dokonania analizy instrukcji warunkowej “if(a!=null)” wystarczy:

var node = tree.GetRoot() .DescendantNodes() .OfType<BlockSyntax>() .First() .DescendantNodes() .OfType<IfStatementSyntax>() .First(); DataFlowAnalysis dataFlowResult = model.AnalyzeDataFlow(node);

Obiekt DataFlowAnalysis zawiera następujące pola:

public abstract class DataFlowAnalysis { public abstract ImmutableArray<ISymbol> VariablesDeclared { get; } public abstract ImmutableArray<ISymbol> DataFlowsIn { get; } public abstract ImmutableArray<ISymbol> DataFlowsOut { get; } public abstract ImmutableArray<ISymbol> AlwaysAssigned { get; } public abstract ImmutableArray<ISymbol> ReadInside { get; } public abstract ImmutableArray<ISymbol> WrittenInside { get; } public abstract ImmutableArray<ISymbol> ReadOutside { get; } public abstract ImmutableArray<ISymbol> WrittenOutside { get; } public abstract ImmutableArray<ISymbol> Captured { get; } public abstract ImmutableArray<ISymbol> UnsafeAddressTaken { get; } public abstract bool Succeeded { get; } }

Jak widzimy, struktura zawiera mnóstwo pól opisujących co dzieje się z danymi we wskazanym bloku, w naszym przypadku w instrukcji warunkowej.

Na przykład WrittenInside zawiera zmienne, które są zapisywane w danym bloku. Dla powyższego przykładu jest to “localA”. VariablesDeclared będzie puste ponieważ nie deklarujemy żadnych zmiennych w powyższym IF. DataFlowsIn zawiera z kolei “localA” oraz “a” ponieważ to one uczestniczą w DataFlow.

SemanticModel jest przydatny jeszcze w kilku innych analizach, o których będę pisał w następnych postach.

Roslyn: Workspace API

Roslyn to nie tylko parsowanie kodu, ale również zarządzanie projektem\solucją za pomocą WorkspaceAPI.

Pisząc różne narzędzia dla programistów, oprócz analizy kodu, zwykle chcemy mieć informacje o kontekście danego kodu – np. nazwie pliku czy projekcie w którym znajduje się dana klasa.

Workspace API, jak nie trudno domyślić się, opiera się na abstrakcyjnej klasie Workspace.  Zwykle jednak pracować będziemy z MSBuildWorkspace, która pozwala nam zarządzać .sln czy .csproj:

MSBuildWorkspace msWorkspace = MSBuildWorkspace.Create(); Solution solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;

Wywołując OpenSolutionAsync na Workspace, otwieramy po prostu solucję. Zmienna solutionPath to zwykły string wskazujący do pliku .sln. Po otwarciu, możemy wylistować wszystkie projekty i pliki:

foreach (Project project in solution.Projects) { Console.WriteLine(project.Name); foreach (Document document in project.Documents) { Console.WriteLine("\t{0}",document.FilePath); } }

API pozwala również na zarządzanie referencjami:

foreach (Project project in solution.Projects) { foreach (ProjectReference projectReference in project.ProjectReferences) { Console.WriteLine(projectReference.ProjectId); } foreach (var metaReference in project.MetadataReferences) { Console.WriteLine(metaReference.ToString()); } }

ProjectReferencje to referencja do innego projektu w tej samej solucji. Z kolei za pomocą MetaReference uzyskujemy dostęp do wszystkich innych bibliotek z których korzystamy, włączając przy tym systemowe biblioteki.

Do dyspozycji jest również AdhocWorkspace, klasa służąca do tworzenia całych solucji i projektów od zera:

var workspace = new AdhocWorkspace(); const string projectName = "ProjectA"; ProjectId projectId = ProjectId.CreateNewId(); ProjectInfo projectInfo = ProjectInfo.Create(projectId, VersionStamp.Create(), projectName, projectName, LanguageNames.CSharp); Project projectA = workspace.AddProject(projectInfo); var classCode = SourceText.From("class SampleClass {}"); var newDocument = workspace.AddDocument(projectA.Id, "SampleClass.cs", classCode);

Jeśli piszemy rozszerzenia do Visual Studio, wtedy zdecydowanie powinniśmy zainteresować się VisualStudioWorkspace. Wcześniej zarządzanie projektem było dość kłopotliwe, a teraz wystarczy, że z rozszerzenia uzyskamy dostęp do  VisualStudioWorkspace za pomocą importu MEF:

[Import(typeof(Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace))] public VisualStudioWorkspace myWorkspace { get; set; }

Jeśli piszemy plugin, liczący metryki kodu, wtedy za pomocą VisualStudioWorkspace wiemy, kiedy użytkownik dodaje nowy plik, projekt czy modyfikuje jakąś konfigurację. Klasa eksponuje zdarzenia takie jak WorkspaceChanged, które wystarczy po prostu monitorować.

Roslyn: Wizualizacja drzewa

Sporo pisałem w ostatnich wpisach o drzewie jako reprezentacji kodu. Za pomocą LINQ albo CSharpSyntaxWalker jesteśmy w stanie wyświetlić jakikolwiek element kodu. Wszelkie metody reprezentowane są za pomocą  MethodDeclarationSyntax,  klasy  z kolei ClassDeclarationSyntax.

Problem w tym, że tych obiektów jest na tyle dużo, że czasami trudno domyślić się na jaki należy rzutować generyczny SyntaxNode, aby odczytać konkretne dane. Na szczęście w raz z instalacją Roslyn, do dyspozycji w Visual Studio będziemy mieli Roslyn Syntax Visualizer.

Plugin standardowo instalujemy z galerii:

https://visualstudiogallery.msdn.microsoft.com/0f18f8c3-ec79-468a-968f-a1a0ee65b388

Z menu głównego wybieramy View –> Other Windows –> Roslyn Syntax Visualizer:

image

Załóżmy, że mamy następujący kod:

using System; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; namespace ConsoleApplication9 { class Program { static void Main(string[] args) { SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public void TestMethod1() { } public void TestMethod1(string a, string b, int c) { if(c==5) { ; ; } } } "); Console.WriteLine(tree.ToString()); } } }

Umieszczając kursor na deklaracji klasy dostaniemy:

image

Analogicznie klikając na elemencie w TreeView, w kodzie zostanie podświetlony dany element:

image

Niebieski kolor to SyntaxNode a zielony SyntaxToken. Jeśli pojęcia nie są jasne to zapraszam do mojego pierwszego wpisu o Roslyn.

Roslyn–przepisywanie kodu

W poprzednim wpisie przedstawiłem klasę CSharpSyntaxWalker – przydatną przy analizie drzewa kodu. Dzięki niej, automatycznie bez pisania kodu rekurencyjnego jesteśmy w stanie przejść przez każdy element kodu.

Dzisiaj o analogicznym rozwiązaniu ale służącym do przepisywania kodu a nie tylko jego analizowania. Mechanizm działa bardzo podobnie do CSharpSyntaxWalker. Wystarczy, że stworzymy klasę dziedziczącą po CSharpSyntaxRewriter:

public class CustomRewriter : CSharpSyntaxRewriter { public override SyntaxNode VisitEmptyStatement(EmptyStatementSyntax node) { return null; } }

Następnie, wystarczy wywołać Visit na węźle, który chcemy przepisać:

SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public void TestMethod1() { } public void TestMethod1(string a, string b, int c) { if(c==5 { ; ; } } } "); var rewritter = new CustomRewriter(); SyntaxNode rewrittenNode = rewritter.Visit(tree.GetRoot());

Po przepisaniu, nowy kod nie będzie zawierał dwóch pustych instrukcji, które widzimy w powyższej instrukcji warunkowej (if(c==5)):

image

Na przykład, w celu wstawienia komentarza przed każdym IF’em możemy:

public class CustomRewriter : CSharpSyntaxRewriter { public override SyntaxNode VisitIfStatement(IfStatementSyntax node) { return base.VisitIfStatement(node).WithLeadingTrivia(SyntaxFactory.Comment("//test")); } }

W narzędziach generujących kod, powyższa klasa jest bardzo przydatna. Na przykład, jeśli chcemy automatycznie wygenerować komentarze dla naszego kodu, wystarczy przeciążyć VisitMethodDeclaration:

public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) { return base.VisitMethodDeclaration(node); }

CSharpSyntaxWalker oraz CsharpSyntaxRewritter są pomocniczymi klasami. Za pomocą LINQ i standardowego API możemy osiągnąć to samo. Jak wspomniałem jednak, wymagałoby to pisania rekurencyjnego kodu i dla skomplikowanych projektów jest to po prostu niewygodne oraz może spowodować problemy z wydajnością.

Roslyn – przeglądanie drzewa

W ostatnim poście pokazałem jak za pomocą LINQ można przeglądać drzewo kodu. Czasami jest to jednak niewygodne. Załóżmy, że piszemy konwerter z C# na jakiś inny język. W takiej sytuacji, prawdopodobnie chcemy mieć dostęp do każdego elementu kodu. Możemy to zrobić za pomocą LINQ, ale jest to mało wygodne. Musielibyśmy pisać kod rekurencyjny, który dla skomplikowanych kodów jest jak wiadomo mało wydajny, a nawet może zakończyć się wyjątkiem OutOfMemory.

Do dyspozycji jednak mamy klasę CSharpSyntaxWalker. Dziedzicząc po niej, możemy przeładować jedną z metod, która jest wywoływana automatycznie, podczas analizy kodu. Przykład:

public class CustomNodeWalker : CSharpSyntaxWalker { private int _level = 0; public override void Visit(SyntaxNode node) { _level++; var indents = new String('\t', _level); Console.WriteLine(indents + node.Kind()); base.Visit(node); _level--; } }

W powyższym przykładzie, przeładowaliśmy Visit, która jest wywoływana dla każdego elementu w danym kodzie. Wywołujemy ją w następujący sposób:

SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public void TestMethod1() { } public void TestMethod1(string a, string b, int c) { if(c==5 { } } } "); var customNodeWalker = new CustomNodeWalker(); customNodeWalker.Visit(tree.GetRoot());

Po wykonaniu, na ekranie zobaczymy:

image

Wyraźnie widzimy, że zostały odwiedzone wszystkie węzły, począwszy od korzenia CompilationUnit.

Czasami chcemy odwiedzić wyłącznie pewne elementy, należące do jakieś grupy. Na przykład, w celu odwiedzenia wszystkich metod wystarczy:

public class CustomNodeWalker : CSharpSyntaxWalker { private int _level = 0; public override void VisitMethodDeclaration(MethodDeclarationSyntax node) { Console.WriteLine(node.Identifier); base.VisitMethodDeclaration(node); } }

Zaglądając do CsharpSyntaxWalker przekonamy się, jakie metody możemy przeciążyć:

public abstract class CSharpSyntaxWalker : CSharpSyntaxVisitor { protected CSharpSyntaxWalker(SyntaxWalkerDepth depth = SyntaxWalkerDepth.Node); public override void Visit(SyntaxNode node); public override void DefaultVisit(SyntaxNode node); public virtual void VisitToken(SyntaxToken token); public virtual void VisitLeadingTrivia(SyntaxToken token); public virtual void VisitTrailingTrivia(SyntaxToken token); public virtual void VisitTrivia(SyntaxTrivia trivia); protected SyntaxWalkerDepth Depth { get; } }

Jak widać, nie trzeba pisać własnego kodu rekurencyjnego. Metoda Visit jest najbardziej szczegółowa i będzie wywoływana nawet dla deklaracji using, pętli, warunków itp. Od samego korzenia, przejdziemy do najmniejszych szczegółów. Dużo wygodniejsze niż LINQ, jeśli jesteśmy zainteresowani całą metodą lub dokumentem, a nie tylko konkretnym elementem.

Roslyn–analiza kodu za pomocą LINQ

Analiza kodu za pomocą LINQ to chyba esencja Roslyn.  Bez Roslyn, kod był dla nas jak zwykły tekst (string) i w przypadku jakiejkolwiek analizy, musieliśmy sami parsować tekst i rozpoznawać odpowiednie fragmenty.

Najpierw tworzymy SyntaxTree za pomocą zwykłego tekstu:

SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public void TestMethod1() { } } public class Class2 { public void TestMethod2() { } } "); SyntaxNode root = tree.GetRoot();

Metoda DescendantNodes zwraca wszystkich potomków, również tych niebezpośrednich. Jeśli chcemy zwrócić wszystkie klasy wystarczy szukać węzłów typu ClassDecelarionSyntax:

foreach (ClassDeclarationSyntax classDeclarationSyntax in root.DescendantNodes().OfType<ClassDeclarationSyntax>()) { Console.WriteLine(classDeclarationSyntax.Identifier); }

Analogicznie, w celu wyświetlenia metod wystarczy:

foreach(ClassDeclarationSyntax classDeclarationSyntax in root.DescendantNodes().OfType<ClassDeclarationSyntax>()) { Console.WriteLine(classDeclarationSyntax.Identifier); foreach (var method in classDeclarationSyntax.DescendantNodes().OfType<MethodDeclarationSyntax>()) { Console.WriteLine("\t{0}",method.Identifier); } }

Jak wspomniałem, DescendantNodes zwraca wszystkich potomków więc możemy wyświetlić metody bezpośrednio korzystając z korzenia:

foreach (MethodDeclarationSyntax methodDeclarationSyntax in root.DescendantNodes().OfType<MethodDeclarationSyntax>()) { Console.WriteLine(methodDeclarationSyntax.Identifier); }

Jeśli chcemy tylko bezpośrednich potomków (czyli dzieci) wtedy korzystamy z ChildNodes:

foreach (MethodDeclarationSyntax methodDeclarationSyntax in root.ChildNodes().OfType<MethodDeclarationSyntax>()) { Console.WriteLine(methodDeclarationSyntax.Identifier); }

Zmodyfikujmy trochę kod, aby zawierał metodę z parametrami:

SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public void TestMethod1() { } public void TestMethod1(string a, string b, int c) { } } ");

Zapytanie zwracające wszystkie metody, które zawierają parametry to:

SyntaxNode root = tree.GetRoot(); MethodDeclarationSyntax methodWithPars = root.DescendantNodes().OfType<MethodDeclarationSyntax>().First(n => n.ParameterList.Parameters.Any()); Console.WriteLine(methodWithPars.Identifier);

Wyświetlenie parametrów sprowadza się do prostej pętli:

foreach (var par in methodWithPars.ParameterList.Parameters) { Console.WriteLine(par.ToString()); }

Często potrzebujemy pójść w drugą strony, to znaczy mamy wskaźnik na potomka, ale chcemy znaleźć rodzica. Do dyspozycji mamy metodę Ancestors:

foreach (var par in methodWithPars.ParameterList.Parameters) { string className = par.Ancestors().OfType<ClassDeclarationSyntax>().Single().Identifier.ToString(); Console.WriteLine("{0}:{1}",className,par.ToString()); }

Za pomocą LINQ możemy uzyskać dostęp do każdego fragmentu kodu, nie tylko deklaracji. Możemy np. wylistować IF”Y:

SyntaxTree tree = CSharpSyntaxTree.ParseText(@" public class Class1 { public void TestMethod1() { } public void TestMethod1(string a, string b, int c) { if(c==5 { } } } "); SyntaxNode root = tree.GetRoot(); var ifStatements = root.DescendantNodes().OfType<IfStatementSyntax>(); foreach (var ifStatementSyntax in ifStatements) { Console.WriteLine(ifStatementSyntax.ToString()); }

Analogicznie sprawa wygląda z wywołaniami metod, klauzulami using, switch\case, pętlami, zwracaniem wartości jak i z KAŻDYM elementem kodu. Innymi słowy drzewo jest na tyle szczegółowe, że “bez problemu” możemy napisać konwerter z C# na jakikolwiek inny język np. JavaScript, bez konieczności ręcznego parsowania kodu C#.

Roslyn–Scripting API

W poprzednim wpisie, w dużym skrócie przedstawiłem po co powstał Roslyn i komu może przydać się. W kilku kolejnych postach, zaprezentuję w praktyce jego najważniejsze funkcje.

Zaczynami od scripting API.  APi szczególnie przydatne w przypadku edytorów do gier czy bardziej zaawansowanych reguł biznesowych. Zacznijmy od klasycznego “hello world”:

CSharpScript.Run("System.Console.WriteLine(\"Hello world\")");

Nie trudno domyślić się, że Run wykona kod przekazany jako String – w tym przypadku jest to po prostu wyświetlenie napisu na ekranie.

Zwykle chcemy jednak zmodyfikować jakieś zmienne:

ScriptState result=CSharpScript.Run("int a=5;int b=5; int c=a+b;"); foreach (ScriptVariable scriptVariable in result.Variables) { Console.WriteLine(string.Format("{0}:{1}", scriptVariable.Name, scriptVariable.Value)); }

Pole Variable będzie zawierać wszelkie zmienne stworzone w skrypcie, wraz z ich wartościami. W tym przypadku będzie to “a:5,b:5,c:10”.

Możliwe jest również przekazywanie parametrów do skryptu. Stwórzmy dowolną strukturę:

public class Data { public int Number = 5; }

Następnie wykonanie skryptu będzie wyglądać następująco:

var data = new Data(); CSharpScript.Run("Number=Number*2", data); Console.WriteLine(data.Number);

Zmienna Number zostanie przekazana z wartością 5, a potem w skrypcie zostanie zmieniona na 10 i zwrócona na zewnątrz. W skrócie jest to argument wejściowo-wyjściowy.

Inna przydatna funkcja to Eval. Uruchamia ona po prostu skrypt i zwraca od razu to co znajduje się w nim na wyjściu. Na przykład, poniższy kod zwróci losowo wygenerowaną liczbę:

object result = CSharpScript.Eval("new Random().Next()"); Console.WriteLine(result.ToString());

To samo można oczywiście osiągnąć za pomocą Run, ale Eval w wielu sytuacjach jest bardziej intuicyjny w użyciu. Zarówno do Eval jak i Run można przekazać ScriptOptions. Bardzo często skrypty potrzebują referencji do zewnętrznych bibliotek i wtedy możemy:

ScriptOptions options = ScriptOptions.Default .AddReferences(Assembly.GetAssembly(typeof(Path))) .AddNamespaces("System.IO"); var currentDir = CSharpScript.Eval(@"Path.GetCurrentDirectory()", options);

Bardzo ciekawą opcją jest skompilowanie skryptu i użycie potem go jako zwykłej delegaty:

ScriptState state = CSharpScript.Run("int Add(int x,int y) { return x +y; }"); var add = state.CreateDelegate<Func<int, int,int>>("Add"); var sum = add(5, 10); Console.Write(sum); // 15

Roslyn – wprowadzanie

O projekcie Roslyn od dawna już słychać, ale jeszcze nigdy nie miałem okazji z niego poważnie korzystać, ani wspomnieć o tym na blogu.

Czym jest więc Roslyn? W skrócie pisząc jest to “compiler as service”. Roslyn dostarcza API za pomocą, którego możemy komunikować się kompilatorem.  Możemy zatem pisać programy, które rozpoznają kod (C#) i mogą go dynamicznie wykonywać. Innymi słowy, dzięki Roslyn jesteśmy w stanie załadować kod w formie czystego tekstu i w pamięci analizować go  już jako strukturę danych czyli drzewo. Dzięki temu odczytanie jakiekolwiek elementu kodu (nazwa klasy, warunek, modyfikator) jest bardzo proste i sprowadza się do wyrażenia LINQ na tej strukturze danych.

Załóżmy, że mamy następujący kod:

using System; using System.Collections.Generic; using System.Linq; using System.Security.Policy; using System.Text; using System.Threading.Tasks; namespace ClassLibrary1 { public class Class1 { public void Test(int argument) { if (argument > 10) { } else { throw new Exception(); } } } }

Instalując Roslyn, Visual Studio zostanie zintegrowany również z Roslyn Syntaax Visualizer – proste narzędzie wyświetlające kod w formie drzewa. Dla powyższego kodu, drzewo wygląda następująco:

image

Widzimy, że każdy nawet najmniejszy element posiada własną strukturę typu ClassDeclartion czy UsingDirective. Za pomocą LINQ możemy odczytać każdy szczegół.

Podczas pracy z Roslyn, pojawi się wiele terminów określających powyższe węzły. Pierwszym z nich jest SyntaxTree czyli wspomniane drzewo prezentujące dany fragment kodu (np. plik cs).

SyntaxNode to z kolei węzeł typu ClassDeclaration czy MethodDeclaration. Klasa czyli “class NazwaKlasy” stanowi ClassDeclarion. Analogicznie sygnatura metody to MethodDeclartion.

SyntaxToken to jeszcze mniejszy element drzewa. Technicznie jest to liść czyli węzeł bez dzieci. Jak wspomniałem, ClassDeclartion to np. “class NazwaKlasy {“ z kolei SyntaToken  dla tego węzła to “class”, “NazwaKlasy” oraz “{“. Widzimy, że SyntaToken to najdrobniejszy element i nie da go robić się na mniejsze.

SyntaxTrivia z kolei to najmniej znaczący element i może być nim spacja, koniec linii czy komentarz.

Za pomocą Roslyn, możemy zatem robić wszystko z kodem, co wymagało wcześniej własnego parsera czyli analizowanie kodu, wykonywanie poszczególnych części czy nawet odczytywanie konkretnych projektów w solucji (tzw. Workspaces API). Roslyn Scripting API dostarcza mechanizm  dynamicznego wykonywania kodu. Możemy przekazać po prostu kod jako tekst i zostanie on wykonany.

Nie wszystkim jednak Roslyn przyda się. Oczywiście największą grupą odbiorców będą twórcy narzędzi programistycznych. Liczenie metryk kodu czy analiza jakości kodu (CodeAnalysis, nDepend) zostanie uproszczona. Analogicznie wszelkie generatory kodu będą mogły teraz być tworzone za pomocą API.

Również aplikacje wykorzystujące C# jako język skryptowy staną się dużo łatwiejsze w implementacji. Na przykład twórcy gier komputerowych często wymagają podobnej funkcjonalności w edytorach dla AI.

Moim zdaniem dla większości programistów jest to ciekawostka, która jednak będzie miały ogromny wpływ na .NET jak i narzędzia, które będą powstawać. W aplikacjach biznesowych używanie Roslyn w czystej postaci raczej nie będzie zbyt częstym scenariuszem, aczkolwiek jestem w stanie wyobrazić sobie wiele sytuacji, gdzie jest to bardzo przydatne (np. silniki reguł biznesowych). Jak wspomniałem jednak, zwykle te aplikacje biznesowe korzystają z gotowych komponentów, które z kolei bardzo prawdopodobne będą bazowały na Roslyn.

W kolejnych postach, przyjrzyjmy się szczegółom API…