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
;
Does it use Reflection? nameof is apparently as efficient as declaring a string variable. No reflection or whatsoever!
Each extern alias declaration introduces an additional root-level namespace that parallels (but does not lie within) the global namespace. Thus types from each assembly can be referred to without ambiguity by using their fully qualified name, rooted in the appropriate namespace-alias.
if you came here for compile-time desision - there is no way at this moment. It is an expected behavior from compilator side as result of expression compilation.
(typeof(f::SomeCustomClassName)).Name
instead of nameof(f::SomeCustomClassName)
Now lets see again to nameof_expression (link)
nameof_expression = 'nameof' '(' named_entity ')'
where named_entity is
named_entity = named_entity_target ('.' identifier type_argument_list?)*
And lets look again to calling class name.
In common variant without aliases we got:
nameof(SomeCustomClassName)
that unfolds to nameof(ConsoleApp1.SomeCustomClassName)
That matches named_entity_target.identifier
.
But the alias nameof(f::SomeCustomClassName)
...
From specification
named_entity_target
: simple_name
| 'this'
| 'base'
| predefined_type
| qualified_alias_member
;
Is qualified_alias_member so named_entity_target = qualified_alias_member
So nameof_expression unfolds to
qualified_alias_member ('.' identifier type_argument_list?)*
Lets have a look to qualified_alias_member (link):
qualified_alias_member
: identifier '::' identifier type_argument_list?
;
According to all of this we have
identifier '::' identifier ('.' identifier type_argument_list?)*
That does not match to nameof(f::SomeCustomClassName)
Thats why it is incorrect for compilator.
Thats why it need for dot
nameof(f::SomeCustomClassName.SomeCustomClassName2)
it is not a bug. It is miscalculation from expression creators or porposive limitation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With