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.