C# Language Design Notes for Jul 9, 2014

Topics: C# Language Design
Coordinator
Jul 21, 2014 at 11:17 PM

C# Language Design Notes for July 9, 2014

Notes are archived here.

Agenda

  1. Detailed design of nameof <details settled>
  2. Design of #pragma warning extensions <allow identifiers>

Details of nameof

The overall design of nameof was decided in the design meeting on Oct 7, 2013. However, a number of issues weren’t addressed at the time.

Syntactic ambiguity

The use of nameof(…) as an expression can be ambiguous, as it looks like an invocation. In order to stay compatible, if there’s an invokable nameof in scope we’ll treat it as an invocation, regardless of whether that invocation is valid. This means that in those cases there is no way to apply the nameof operator. The recommendation of course will be to get rid of any use of nameof as an identifier, and we should think about having diagnostics helping with that.

Which operands are allowed?

The symbols recognized in a nameof expression must represent locals, range variables, parameters, type parameters, members, types or namespaces. Labels and preprocessor symbols are not allowed in a nameof expression.

In general, free-standing identifiers are looked up like simple names, and dotted rightmost identifiers are looked up like member access. It is thus an error to reference locals before their declaration, or to reference inaccessible members. However, there are some exceptions:

All members are treated as if they were static members. This means that instance members are accessed by dotting off the type rather than an instance expression. It also means that the accessibility rules around protected instance members are the simpler rules that apply to static members.

Generic types are recognized by name only. Normally there needs to be a type parameter list (or at least dimension specifier) to disambiguate, but type parameter lists or dimension specifiers are not needed, and in fact not allowed, on the rightmost identifier in a nameof.

Ambiguities are not an error. Even if multiple entities with the same name are found, nameof will succeed. For instance, if a property named M is inherited through one interface and a method named M is inherited through another, the usual ambiguity error will not occur.

The referenced set

Because ambiguities are allowed, a nameof operator can reference a set of different entities at the same time. The precise set of referenced entities in the presence of ambiguity can be loosely defined as “those it would be ambiguous between”. Thus, shadowed members or other entities that wouldn’t normally be found by lookup, e.g. because they are in a base class or an enclosing scope of where an entity is found, will not be part of the referenced set.

The notion of referenced set has little importance for the language-level semantics, but is important for the tooling experience, e.g. for refactorings, go-to-definition, etc.
Reference to some entities, e.g. obsolete members, Finalize or ‘op_’ methods, is normally an error. However, it is not an error in nameof(…) unless all members of the referenced set would give an error. If all non-error references give warnings, then a warning is given.

The resulting string

C# doesn’t actually have a notion of canonical name. Instead, equality between names is currently defined directly beween names that may contain special symbols.

For nameof(… i) we want the resulting string to be the identifier I given, except that formatting characters are omitted, and Unicode escapes are resolved. Also, any leading @ is removed.

In the case of aliases, this means that those are not resolved to their underlying meaning: the identifier is that of the alias itself.

As a result, the meaning of the identifier is always only used to check if it is valid, never to decide what the resulting string is. There is no semantic component to determining the result of a nameof operator, only to determining if it is allowed.

Pragma warning directives

Now that custom diagnostics are on their way, we want to allow users to turn these on and off from source code, just as we do with the compiler’s own diagnostics today. To allow this, we need to extend the model of how a diagnostic is identified: today a number is used, but that is not a scalable model when multiple diagnostic providers are involved.

Instead the design is that diagnostics are identified by an identifier. For compatibility the C# compiler’s own diagnostics can still be referenced with a number, but can also be referred to with the pattern CS1234:
#pragma warning disable AsyncCoreSet
#pragma warning disable CS1234
Jul 26, 2014 at 11:26 PM
#pragma warning disable AsyncCoreSet
#pragma warning disable CS1234
This produces warnings:
warning CS1072: String or numeric literal expected
I think what you actually mean is:
#pragma warning disable "AsyncCoreSet"
#pragma warning disable "CS1234"
By the way, currently Roslyn language service produces the string-based suppressions for built-in compiler warnings - this is fine, but what some project is targeting C# <= 5.0 and somebody in team uses C# <= 5.0 compiler? Why not always use numerical ids for built-in compiler warnings and string-based for any other diagnostics?
Jul 27, 2014 at 9:44 AM
About nameof(), it will be really interesting to be able use it in switch statements:
string Validate(PropertyInfo pi){
    switch(pi.Name)
    {
          case nameof(Name): { if(Name.Length  < 3) return "Name to sort"; }
          case nameof(DOB): ....
          case nameof(Address): ....
    }
    return null; 
}
Jul 29, 2014 at 9:00 PM
I'm concerned about the fact that it always returns the simple name. I feel like if you pass in a fully qualified token, that it should return a fully qualified name string.

As it is proposed, I can imagine something like this happening to get a fully qualified name:
BindToFullyQualifiedName(nameof(Microsoft) + "." + nameof(Microsoft.Data) + "." + nameof(Microsoft.Data.Entities) + "." + nameof(Microsoft.Data.Entities.EntityObject));
This would probably be worse than just using the existing method of paying a runtime cost to get the name you're looking for.
Jul 30, 2014 at 6:04 AM
Edited Jul 30, 2014 at 6:04 AM
MgSam wrote:
I'm concerned about the fact that it always returns the simple name. I feel like if you pass in a fully qualified token, that it should return a fully qualified name string.
I think typeof(EntityObject).FullName is fully sufficient in your case.
Jul 30, 2014 at 9:50 PM
I love this idea.

Why not add fullnameof() as well?
Aug 1, 2014 at 10:45 PM
Edited Aug 1, 2014 at 10:48 PM
Just want to throw out the idea of having "nameof()" with no operand return the name of the current member (or type). It seems like this is a pretty common scenario, and could be more resilient of rename refactorings.
As an example, if you have two overloads, and rename one of them:
void Foo(int i) {
    var me = nameof(Foo);
}
void Foo(bool b) { 
    var me = nameof(Foo); 
}
If you rename one from foo to bar, 'me' will still have the value 'foo', since it is still a valid name. With the empty nameof(), it would become 'bar' which is probably what was expected.

To consider: that happens if you were to use nameof() in an anonymous method? Should it be an error? Return an empty string? Return a compiler generated name for the anonymous method? Return the name of the closest enclosing named unit (member or type)? Is there a scenario where one wouldn't exist?
Aug 2, 2014 at 1:08 AM
Hi MarkPflung, I think your problem is already solved in C# 5.0 (using a helper method) with caller info attributes.
Aug 3, 2014 at 4:44 PM
MarkPflug wrote:
To consider: that happens if you were to use nameof() in an anonymous method? Should it be an error? Return an empty string? Return a compiler generated name for the anonymous method? Return the name of the closest enclosing named unit (member or type)? Is there a scenario where one wouldn't exist?
In the current design, anonymous methods just have no name (it's what anonymous means, right?), so there is no syntactic possibility to nameof such a method. But if even such a possibility would exist, what do you need it for? You don't want to simply call into a lambda with reflection, as such calling needs a special setup anyway (manual lifting of variables into a helper class, anyone?). So the only valid use I would see is logging function's name (which is anyway not possible as the developer didn't bother to assign one).
Aug 4, 2014 at 4:54 PM
Edited Aug 4, 2014 at 4:55 PM
Olmno: I agree, that this example can be achieved with caller info. To me, this feature seems to overlap with caller info, in that it would most often be used for either reflection or diagnostics. However, I think the big difference is that nameof() could be used as a const value; meaning it could be used as a switch case label I guess (as in your previous example), where a helper method could not.

VladD: You might find it interesting to use Olmo's point about caller info to explore the behavior of anonymous functions and caller info:
class Program
{   
    static Action A = () => { S(); };

    static void Main(string[] args) {
        Action a = () => { S(); };
        a();
        A();
    }

    static void S([CallerMemberName] string name = null) {
        Console.WriteLine(name);
    }
}
You might find the output of this code interesting (I did). I think that my proposed nameof() should behave similarly.
Aug 6, 2014 at 1:55 PM
Am I overlooking a way to get the current type easily with nameof?

Caller info not being able to get the type name is often a headache.
Aug 6, 2014 at 4:05 PM
KathleenDollard wrote:
Caller info not being able to get the type name is often a headache.
Agreed, I always thought caller type fullname would be vastly more useful than the filename/line number.

Cast a vote:
https://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2252418-caller-info-attribute-callermembernamespace
Sep 22, 2014 at 9:14 PM
One usage that comes to mind for nameof(...) is argument validation:
void foo(string x)
{
if(x == null) throw new ArgumentNullException(nameof(x));
...
}

However, because this line is very common, I would want to extract it to a shared method. So what I would really want to write is something like this:

void foo(string x)
{
ValidateNotNull(x);
...

However, I don't find how nameof helps me in this case, even if I would write:
ValidateNotNull(nameof(x));

Am I missing another way to achieve this?
Sep 23, 2014 at 8:56 AM
I don't think you could do better than
ValidateNotNull(x, nameof(x));
However instead of adding more value to the nameof operator I'd rather see this solved by extending the precondition/postcondition syntax. That way it would be more informative on the consumer side too.
Oct 8, 2014 at 8:15 AM
Usage scenario for nameof should include usage in CustomAttributes.
[Validation(typeof(CustomValidator), nameof(CustomValidator.Validate))]