Hasta este punto, hemos estado trabajando con código C# en un nivel puramente sintáctico. Podemos encontrar declaraciones de propiedades, pero no podemos rastrear referencias a esta propiedad dentro de nuestro código fuente. Podemos identificar invocaciones, pero no podemos decir qué se está invocando. Y Dios nos ayude si queremos tratar de resolver los problemas realmente difíciles como la resolución de sobrecarga.
La capa semántica es donde realmente brilla el poder de Roslyn. El modelo semántico de Roslyn puede responder todas las preguntas difíciles de tiempo de compilación que podamos tener. Sin embargo, este poder tiene un costo. Consultar el modelo semántico suele ser más costoso que consultar los árboles de sintaxis. Esto se debe a que solicitar un modelo semántico a menudo desencadena una compilación.
Hay 3 formas diferentes de solicitar el modelo semántico:
1. Document.GetSemanticModel()
2. Compilation.GetSemanticModel(SyntaxTree)
3. Diferentes contextos como AnalysisContexts, CodeBlockStartAnalysisContext.SemanticModel y SemanticModelAnalysisContext.SemanticModel
Para evitar el problema de configurar nuestro propio espacio de trabajo, simplemente crearemos compilaciones para árboles de sintaxis individuales de la siguiente manera:
var tree = CSharpSyntaxTree.ParseText(@"
public class MyClass
{
int MyMethod() { return 0; }
}");
var Mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var compilation = CSharpCompilation.Create("MyCompilation",
syntaxTrees: new[] { tree }, references: new[] { Mscorlib });
var model = compilation.GetSemanticModel(tree);
Antes de continuar, vale la pena tomarse un momento para analizar los Símbolos. Los programas de C# se componen de elementos únicos, como tipos, métodos, propiedades, etc. Los símbolos representan casi todo lo que el compilador sabe sobre cada uno de estos elementos únicos.
En un nivel alto, cada símbolo contiene información sobre:
- Donde estos elementos se declaran en fuente o metadatos (Puede haber venido de un ensamblado externo)
- Dentro de qué espacio de nombres y tipo existe este símbolo
- Varias verdades acerca de que el símbolo es abstracto, estático, sellado, etc.
También se puede descubrir otra información más dependiente del contexto. Cuando se trata de métodos, IMethodSymbol nos permite determinar:
- Si el método oculta un método base.
- El símbolo que representa el tipo de retorno del método.
- El método de extensión del que se sobreescribio este símbolo.
El modelo semántico es nuestro puente entre el mundo de la sintaxis y el mundo de los símbolos.
SemanticModel.GetDeclaredSymbol() acepta la sintaxis de declaración y proporciona el símbolo correspondiente.
SemanticModel.GetSymbolInfo() acepta la sintaxis de expresión (p. ej., InvocationExpressionSyntax) y devuelve un símbolo. Si el modelo no pudo resolver con éxito un símbolo, proporciona símbolos candidatos que pueden servir como mejores conjeturas.
A continuación, recuperamos el símbolo de un método a través de su sintaxis de declaración. Luego recuperamos el mismo símbolo, pero a través de una invocación (InvocaciónExpresiónSyntax) en su lugar.
var tree = CSharpSyntaxTree.ParseText(@"
public class MyClass {
int Method1() { return 0; }
void Method2()
{
int x = Method1();
}
}
}");
var Mscorlib = PortableExecutableReference.CreateFromAssembly(typeof(object).Assembly);
var compilation = CSharpCompilation.Create("MyCompilation",
syntaxTrees: new[] { tree }, references: new[] { Mscorlib });
var model = compilation.GetSemanticModel(tree);
//Looking at the first method symbol
var methodSyntax = tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>().First();
var methodSymbol = model.GetDeclaredSymbol(methodSyntax);
Console.WriteLine(methodSymbol.ToString()); //MyClass.Method1()
Console.WriteLine(methodSymbol.ContainingSymbol); //MyClass
Console.WriteLine(methodSymbol.IsAbstract); //false
//Looking at the first invocation
var invocationSyntax = tree.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>().First();
var invokedSymbol = model.GetSymbolInfo(invocationSyntax).Symbol; //Same as MyClass.Method1
Console.WriteLine(invokedSymbol.ToString()); //MyClass.Method1()
Console.WriteLine(invokedSymbol.ContainingSymbol); //MyClass
Console.WriteLine(invokedSymbol.IsAbstract); //false
Console.WriteLine(invokedSymbol.Equals(methodSymbol)); //true
Una instancia de SemanticModel almacena en caché símbolos locales e información semántica. Por lo tanto, es mucho más eficiente usar una sola instancia de SemanticModel cuando se hacen varias preguntas sobre un árbol de sintaxis, porque la información de la primera pregunta se puede reutilizar. Esto también significa que mantener una instancia de SemanticModel durante mucho tiempo puede evitar que una cantidad significativa de memoria se recopile como basura.
Esencialmente, Roslyn le permite hacer el equilibrio entre la memoria y la computación. Al consultar el modelo semántico de forma repetitiva, puede ser de su interés mantener una instancia del mismo, en lugar de solicitar un nuevo modelo de una compilación o documento.