Imagine a type at the root namespace level (could be in the default global
space, or could potentially be an extern alias
).
It appears that this type cannot be referred to via nameof()
, when using the alias prefix. It works fine with typeof
, and via using
aliases (although nameof
on a using
alias yields the alias name, not the type name). The compiler objects with CS8083, "An alias-qualified name is not an expression."
But: is there a reason for this? is this trying to prevent some obscure problem scenario? or meet some specification minutae? or is it perhaps a compiler bug? I'm also fully content to note that we shouldn't usually declare types in the namespace root - CA1050
is very right about this; but that's not the point here :)
Full example follows; note that in this example uses two projects for the using
alias check, but that for simplicity everything involving C
can just be ignored for a simple investigation.
extern alias foo;
using System;
using X = global::A;
using Y = global::FunWithNamespaces.B;
using Z = foo::C;
public class A { }
namespace FunWithNamespaces
{
public class B { }
public class Program
{
static void Main()
{
// oddness is on the lines marked ## CS8083
// relative-qualified using typeof
Console.WriteLine(typeof(X).Name); // A, expected
Console.WriteLine(typeof(Y).Name); // B, expected
Console.WriteLine(typeof(Z).Name); // C, expected
Console.WriteLine(typeof(A).Name); // A
Console.WriteLine(typeof(B).Name); // B
// note: can't talk about C without using an alias qualifier or a using-alias
Console.WriteLine(typeof(Console).Name); // Console
// relative-qualified things using nameof
Console.WriteLine(nameof(X)); // X; I'm on the fence about X vs A, but... whatever
Console.WriteLine(nameof(Y)); // Y; I'm on the fence about Y vs B, but... whatever
Console.WriteLine(nameof(Z)); // Z; I'm on the fence about Z vs C, but... whatever
Console.WriteLine(nameof(A)); // A
Console.WriteLine(nameof(B)); // B
// note: can't talk about C without using an alias qualifier or a using-alias
Console.WriteLine(nameof(Console)); // Console
// alias-qualified things using typeof
Console.WriteLine(typeof(global::A).Name); // A
Console.WriteLine(typeof(global::FunWithNamespaces.B).Name); // B
Console.WriteLine(typeof(foo::C).Name); // C
Console.WriteLine(typeof(global::System.Console).Name); // Console
// alias-qualified things using nameof
// ??? Console.WriteLine(nameof(global::A)); // ## CS8083 An alias-qualified name is not an expression
Console.WriteLine(nameof(global::FunWithNamespaces.B)); // B
// ??? Console.WriteLine(nameof(foo::C)); // ## CS8083 An alias-qualified name is not an expression
Console.WriteLine(nameof(global::System.Console)); // Console
}
}
}
where C
is defined in a separate assembly and referenced with aliases specified as foo
, and is simply:
public class C { }
Edit: in terms of the specification, this comes down to https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#nameof-expressions, where a nameof_expression
must be either a simple_name
(which it isn't) or a named_entity_target '.' identifier type_argument_list?
- so: for all cases that aren't a simple name, there must be a .something
- but I guess the underlying question here is why must there be a .something
, vs some other construction that permits global::Foo
? For example:
nameof_expression
: 'nameof' '(' named_entity ')'
;
named_entity
: simple_name
| named_entity_target '.' identifier type_argument_list?
| qualified_alias_member type_argument_list?
;
named_entity_target
: 'this'
| 'base'
| named_entity
| predefined_type
| qualified_alias_member
;
global::C
is valid though. Maybe add a comment to the GitHub issue. – Hugelynameof_expression
- the real question is "why is that rule like it is?" - not "why is the compiler following the rule?"; I've clarified with citations – Lactic