One recurring problem I’ve seen people run into with Roslyn is working with fields and symbols. Consider the following:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyClass | |
{ | |
int myField = 0; | |
public int MyProperty {get; set;} | |
public void MyMethod() { } | |
} |
The above program consists of a ClassDecarationSyntax
with child FieldDeclarationSyntax
, PropertyDeclarationSyntax
and MethodDeclarationSyntax
.
In previous blog posts, we discussed how we could use SemanticModel.GetDeclaredSymbol(SyntaxNode)
to retrieve the symbol for pieces of declaration syntax. So it would make sense if we could get the symbols for our field, property and method with the same approach.
Typically one would try the following:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var tree = CSharpSyntaxTree.ParseText(@" | |
class MyClass | |
{ | |
int myField = 0; | |
public int MyProperty {get; set;} | |
public void MyMethod() { } | |
}"); | |
var Mscorlib = PortableExecutableReference.CreateFromAssembly(typeof(object).Assembly); | |
var compilation = CSharpCompilation.Create("MyCompilation", | |
syntaxTrees: new[] { tree }, references: new[] { Mscorlib }); | |
var model = compilation.GetSemanticModel(tree); | |
//Get declarations | |
var property = tree.GetRoot().DescendantNodes().OfType<PropertyDeclarationSyntax>().Single(); | |
var method = tree.GetRoot().DescendantNodes().OfType<PropertyDeclarationSyntax>().Single(); | |
var field = tree.GetRoot().DescendantNodes().OfType<FieldDeclarationSyntax>().Single(); | |
//Get symbols | |
var propertySymbol = model.GetDeclaredSymbol(property); | |
var methodSymbol = model.GetDeclaredSymbol(method); | |
var fieldSymbol = model.GetDeclaredSymbol(field); |
However, there’s a problem here. fieldSymbol
is null! Our approach worked for methods and properties, but didn’t for fields. The reason for this is actually quite simple:
Fields can contain multiple symbols.
For example:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyClass | |
{ | |
int myField1, myField2, myField3; | |
} |
This is even clearer when we look at the syntax tree (I’ve omitted tokens and trivia).
What symbol could be returned for the above FieldDeclarationSyntax
? In order to access these symbols we instead look at the individual variables within the field as shown below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var tree = CSharpSyntaxTree.ParseText(@" | |
class MyClass | |
class MyClass | |
{ | |
int myField1, myField2, myField3; | |
}"); | |
var Mscorlib = PortableExecutableReference.CreateFromAssembly(typeof(object).Assembly); | |
var compilation = CSharpCompilation.Create("MyCompilation", | |
syntaxTrees: new[] { tree }, references: new[] { Mscorlib }); | |
var model = compilation.GetSemanticModel(tree); | |
var field = tree.GetRoot().DescendantNodes().OfType<FieldDeclarationSyntax>().Single(); | |
foreach (var variable in field.Declaration.Variables) | |
{ | |
//Now we can access each of the symbols within the field | |
var fieldSymbol = model.GetDeclaredSymbol(variable); | |
} |
It turns out fields are not the only “special syntax” that cannot be converted into a symbol. If you’re interesting, you can see them all online on the Roslyn Reference Source. They are:
- Global Statements – Global statements don’t declare anything, even though they inherit from MemberDeclarationSyntax.
- IncompleteMembers – Incomplete members don’t declare any symbols.
- Event Field Declaration – Can contain multiple variable declarators. GetDeclaredSymbol should be called on them (the declarators) directly.
- Field Declaration – Can contain multiple variable declarators. GetDeclaredSymbol should be called on them (the declarators) directly.
This bit me in August and I submitted an Issue to the Roslyn team about this. I originally thought an exception should be thrown in these cases, but I’ve since changed my mind. Instead, I think there needs to be clearer documentation on the GetDeclaredSymbol() function. It also might be appropriate for someone to create an analyzer that detects when people do this and warn them.