Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can nameof not be used with alias-qualified types at the root level?

Tags:

c#

nameof

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
    ;
like image 730
Marc Gravell Avatar asked Sep 09 '21 10:09

Marc Gravell


People also ask

Does Nameof use reflection?

Does it use Reflection? nameof is apparently as efficient as declaring a string variable. No reflection or whatsoever!

What is extern alias in C#?

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.


1 Answers

Answer to underlying question

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.

Quick runtime solution

(typeof(f::SomeCustomClassName)).Name instead of nameof(f::SomeCustomClassName)

Explanation

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.

like image 199
Leonid Pavlov Avatar answered Oct 23 '22 16:10

Leonid Pavlov