La idea principal sobre Syntax Trees es que dada una cadena que contiene código C#, el compilador crea una representación de árbol (llamada Árbol de sintaxis) de la cadena. El poder de Roslyn es que nos permite consultar este árbol de sintaxis con LINQ.
Aquí hay una muestra en la que usamos Roslyn para crear un árbol de sintaxis a partir de una cadena. Debemos agregar referencias a Microsoft.CodeAnalysis y Microsoft.CodeAnalysis.CSharp.
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
var tree = CSharpSyntaxTree.ParseText(@"
public class MyClass
{
public void MyMethod()
{
}
}");
var syntaxRoot = tree.GetRoot();
var MyClass = syntaxRoot.DescendantNodes().OfType<ClassDeclarationSyntax>().First();
var MyMethod = syntaxRoot.DescendantNodes().OfType<MethodDeclarationSyntax>().First();
Console.WriteLine(MyClass.Identifier.ToString());
Console.WriteLine(MyMethod.Identifier.ToString());
Primero comenzamos analizando una cadena que contiene código C# y obteniendo la raíz de este árbol de sintaxis. Desde este punto, es extremadamente fácil recuperar elementos que nos gustaría usar LINQ. Dada la raíz del árbol, miramos todos los objetos descendientes y los filtramos por su tipo. Si bien solo hemos usado ClassDeclarationSyntax y MethodDeclarationSyntax, hay piezas correspondientes de sintaxis para cualquier característica de C#.
Intellisense de Visual Studio es extremadamente valioso para explorar los diversos tipos de sintaxis de C# que podemos usar.
Podemos componer expresiones LINQ más avanzadas como cabría esperar:
var tree = CSharpSyntaxTree.ParseText(@"
public class MyClass
{
public void MyMethod()
{
}
public void MyMethod(int n)
{
}
}");
var syntaxRoot = tree.GetRoot();
var MyMethod = syntaxRoot.DescendantNodes().OfType<MethodDeclarationSyntax>()
.Where(n => n.ParameterList.Parameters.Any()).First();
//Find the type that contains this method
var containingType = MyMethod.Ancestors().OfType<TypeDeclarationSyntax>().First();
Console.WriteLine(containingType.Identifier.ToString());
Console.WriteLine(MyMethod.ToString());
Arriba, comenzamos buscando todos los métodos y luego filtramos por aquellos que aceptan parámetros. Luego tomamos este método y avanzamos hacia arriba a través del árbol con el método Ancestors(), buscando el primer tipo que contiene este método.
Existen algunas limitaciones en el tipo de información que puede descubrir a un nivel puramente sintáctico y para superarlas debemos hacer uso del modelo semántico de Roslyn.