I was experimenting with nameof
with generics. I didn't get the result I was expecting. I'm not sure if this is part of the spec or not.
class MainClass
{
public static void Main (string[] args)
{
Console.WriteLine ($"Hello { nameof(FooBar<string>)! }");
}
}
class FooBar<T> { }
The output I get is
Hello FooBar!
I would expect some details about the type parameters.
I tried it with a method and that fails with a compiler error:
class MainClass
{
public static void Main (string[] args)
{
Console.WriteLine ($"Hello { nameof(Do<string>) }");
}
public static T Do<T>() {}
}
Error CS8084: An argument to nameof operator cannot be method group with type arguments (CS8084) (foo)
Is this because nameof
is a compile-time construct and generics are types initialized at runtime? Or is there some other limitation?
I would expect some details about the type parameters
The "spec" says:
Result of nameof. The result of nameof depends on the symbols that its
argument bound to:
One or more members: if all members have the same metadata name then
the result of nameof is that name; otherwise it is an error "This
argument refers to multiple elements with different names". The
metadata name of a member Ior
I< isA1...AK>` is simply "I" after standard
identifier transformations have been applied.
The <T>
parameter is removed due to standard identifier transformations (section §2.4.2 in the C# specification) which doesn't permit <>
as valid identifiers. First any leading @ is removed, then Unicode escape sequences are transformed, and then any formatting characters are removed. This of course still happens at compile-time. You can also see this when you try to print out the name of a generic type:
typeof(List<string>).Name;
Will result in:
List`1
Is this because nameof is a compile-time construct and generics are
types initialized at runtime? Or is there some other limitation?
The second error is specified as invalid by design to avoid overload resolution complications inside nameof
:
Allow generic type arguments? Presumably 'yes' when naming a type
since that's how expression binding already works. And presumably 'no.'
when naming a method-group since type arguments are used/inferred
during overload resolution, and it would be confusing also to have to
deal with that in nameof.
We can see that clearly in the roslyn codebase:
private BoundExpression BindNameofOperatorInternal(InvocationExpressionSyntax node,
DiagnosticBag diagnostics)
{
CheckFeatureAvailability(node.GetLocation(), MessageID.IDS_FeatureNameof, diagnostics);
var argument = node.ArgumentList.Arguments[0].Expression;
string name = "";
// We relax the instance-vs-static requirement for top-level member access expressions by creating a NameofBinder binder.
var nameofBinder = new NameofBinder(argument, this);
var boundArgument = nameofBinder.BindExpression(argument, diagnostics);
if (!boundArgument.HasAnyErrors && CheckSyntaxForNameofArgument(argument, out name, diagnostics) && boundArgument.Kind == BoundKind.MethodGroup)
{
var methodGroup = (BoundMethodGroup)boundArgument;
if (!methodGroup.TypeArgumentsOpt.IsDefaultOrEmpty)
{
// method group with type parameters not allowed
diagnostics.Add(ErrorCode.ERR_NameofMethodGroupWithTypeParameters, argument.Location);
}
else
{
nameofBinder.EnsureNameofExpressionSymbols(methodGroup, diagnostics);
}
}
return new BoundNameOfOperator(node, boundArgument, ConstantValue.Create(name), Compilation.GetSpecialType(SpecialType.System_String));
}