The nameof
operator has gone through five iterations as the Roslyn team worked to nail down its syntax and semantics. Now that the design of the nameof
operator has been finalized, we can look at some simple examples.
Within C#, nameof
is a contextual keyword. This means there is no way to distinguish the nameof
keyword from a call to a method that happens to be named nameof
.
Lucian Wischik elaborates:
In C#, nameof is stored in a normal InvocationExpressionSyntax node with a single argument. That is because in C# ‘nameof’ is a contextual keyword, which will only become the “nameof” operator if it doesn’t already bind to a programmatic symbol named “nameof”
Identifying nameof
Expressions
This means we can only identify nameof
expressions at the semantic level. We do so by finding all invocations to “nameof” that do not bind to any symbol. These invocations must also be standalone (ie. not part of a member access like MyClass.nameof()
)
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 C | |
{ | |
void M() | |
{ | |
int variable = 0; | |
//Does not bind to a symbol (as there is no class called MissingClass) | |
//but it is not a true nameof expression | |
MissingClass.nameof(x); | |
} | |
}"); | |
var Mscorlib = PortableExecutableReference.CreateFromAssembly(typeof(object).Assembly); | |
var compilation = CSharpCompilation.Create("MyCompilation", | |
syntaxTrees: new[] { tree }, references: new[] { Mscorlib }); | |
var model = compilation.GetSemanticModel(tree); | |
var nameofInvocations = tree.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>(); | |
var validNameOfs = nameofInvocations.Where(n => n.Ancestors().OfType<MemberAccessException>().Count() == 0); | |
foreach (var validNameOfSyntax in validNameOfs) | |
{ | |
if (model.GetSymbolInfo(validNameOfSyntax).Symbol == null) | |
{ | |
//validNameOfSyntax is the nameof operator | |
Console.WriteLine("We've found a nameof!"); | |
} | |
} |
As with all contextual keywords, it’s a bit of a pain to work with. But that’s the price we pay for backwards compatability.
Creating a nameof
expression
Previous versions of the Roslyn API allowed for direct creation of a NameOfExpressionSyntax
. Now we must create an InvocationExpressionSyntax
with the identifer “nameof”.
For example, we can generate the following nameof expression:
string result = nameof(result);
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 nameOfExpression = SyntaxFactory.LocalDeclarationStatement( | |
SyntaxFactory.VariableDeclaration( | |
SyntaxFactory.PredefinedType( | |
SyntaxFactory.Token( | |
SyntaxKind.StringKeyword))) | |
.WithVariables( | |
SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>( | |
SyntaxFactory.VariableDeclarator( | |
SyntaxFactory.Identifier( | |
@"result")) | |
.WithInitializer( | |
SyntaxFactory.EqualsValueClause( | |
SyntaxFactory.InvocationExpression( | |
SyntaxFactory.IdentifierName( | |
@"nameof")) | |
.WithArgumentList( | |
SyntaxFactory.ArgumentList( | |
SyntaxFactory.SingletonSeparatedList<ArgumentSyntax>( | |
SyntaxFactory.Argument( | |
SyntaxFactory.IdentifierName( | |
@"result")))))))))); |
Side Note: As a mere mortal, I have no idea how to work with the SyntaxFactory API. I generated the above code with Kirill Osenkov’s fantastic Roslyn Quoter tool.
The important takeaway is that (at the syntax level) nameof
expressions are no different than regular invocations.