Learn Roslyn Now: Part 7 Introducing the Semantic Model

Up until this point we’ve been working with C# code on a purely syntactical level. We can find property declarations, but we can’t track down references to this property within our source code. We can identify invocations, but we can’t tell what’s being invoked. And God help us if we want to try to solve the really hard problems like overload resolution.

In this developer’s opinion, the semantic layer is where the power of Roslyn really shines. Roslyn’s semantic model can answer all the hard compile-time questions we might have. However, this power comes at a cost. Querying the semantic model is typically more expensive than querying syntax trees. This is because requesting a semantic model often triggers a compilation.

There are 3 different ways to request the semantic model:

1. Document.GetSemanticModel()
2. Compilation.GetSemanticModel(SyntaxTree)
3. Various Diagnostic AnalysisContexts including CodeBlockStartAnalysisContext.SemanticModel and SemanticModelAnalysisContext.SemanticModel

To avoid the boiler plate involved in setting up our own Workspace, we’ll simply create compilations for individual syntax trees as follows:


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 });
//Note that we must specify the tree for which we want the model.
//Each tree has its own semantic model
var model = compilation.GetSemanticModel(tree);

view raw

gistfile1.cs

hosted with ❤ by GitHub

Symbols

Before continuing, it’s worth taking a moment to discuss Symbols.

C# programs are comprised of unique elements, such as types, methods, properties and so on. Symbols represent most everything the compiler knows about each of these unique elements.

At a high level, every symbol contains information about:

  • Where this elements is declared in source or metadata (It may have come from an external assembly)
  • What namespace and type this symbol exists within
  • Various truths about the symbol being abstract, static, sealed etc.
  • More information may be found in ISymbol.

Other, more context-dependent information may also be uncovered. When dealing with methods, IMethodSymbol allows us to determine:

  • Whether the method hides a base method.
  • The symbol representing the return type of the method.
  • The extension method from which this symbol was reduced.

Requesting Symbols

The semantic model is our bridge between the world of syntax and the world of symbols.

SemanticModel.GetDeclaredSymbol() accepts declaration syntax and provides the corresponding symbol.

SemanticModel.GetSymbolInfo() accepts expression syntax  (eg. InvocationExpressionSyntax) and returns a symbol. If the model could not successfully resolve a symbol, it provides candidate symbols which can serve as best guesses.

Below, we retrieve the symbol for a method via it’s declaration syntax. We then retrieve the same symbol, but via an invocation (InvocationExpressionSyntax) instead.


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

view raw

gistfile1.cs

hosted with ❤ by GitHub

Note on performance:

The documentation for SemanticNode notes the following:

An instance of SemanticModel caches local symbols and semantic information. Thus, it is much more efficient to use a single instance of SemanticModel when asking multiple questions about a syntax tree, because information from the first question may be reused. This also means that holding onto an instance of SemanticModel for a long time may keep a significant amount of memory from being garbage collected.

Essentially, Roslyn is allowing you to make the tradeoff between memory and computation. When querying the  semantic model repetitively, it may be in your best interest to keep an instance of it around, instead of requesting a new model from a compilation or document.

Next Time

We’ve only scratched the surface of the Semantic Model. Next time we’ll take a look at the control and data flow analysis APIs.

7 thoughts on “Learn Roslyn Now: Part 7 Introducing the Semantic Model

  1. PortableExecutableReference.CreateFromAssembly is now obsolete. CreateFromFile appears to be the preferred option.

  2. Just curious if there is a faster way to get the set of errors than calling compilation.GetDiagnostics() ? Or maybe a hint as to why it’s so slow? On a simple example it’s taking over 1.1sec to return. To get the diagnostics from the syntax tree it only takes around 50-60msec. It looks like it might be a one-time hit (while it loads additional dlls), but I’m not sure that there’s a way to reduce it without keeping the process alive/etc. (Note that I’m trying this with .NET Core, so there could be something with it…) Thanks!

  3. Looks like it’s something to do with using CodeAnalysis from .NET Core. The same test in a .NET Framework console app completes GetDiagnostics() in ~230msec…

    1. Each Project has a compilation.

      Each Document has a semantic model.

      So for your case you should be able to go: Workspace > Solution > Project > Document > SemanticModel.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s