737 строки
33 KiB
Markdown
737 строки
33 KiB
Markdown
# nameof operator: spec v5
|
|
The nameof(.) operator has the form nameof(expression). The expression must have a name, and may refer to either a single symbol or a method-group or a property-group. Depending on what the argument refers to, it can include static, instance and extension members.
|
|
|
|
This is v5 of the spec for the "nameof" operator. [[v1](https://roslyn.codeplex.com/discussions/552376), [v2](https://roslyn.codeplex.com/discussions/552377), [v3](https://roslyn.codeplex.com/discussions/570115), [v4](https://roslyn.codeplex.com/discussions/570364)]. The key decisions and rationales are summarized below. _Please let us know what you think!_
|
|
|
|
|
|
# Rationale
|
|
|
|
Question: why do we keep going back-and-forth on this feature?
|
|
|
|
Answer: I think we're converging on a design. It's how you do language design! (1) make a proposal, (2) _spec it out_ to flush out corner cases and make sure you've understood all implications, (3) _implement the spec_ to flush out more corner cases, (4) try it in practice, (5) if what you hear or learn at any stage raises concerns, goto 1.
|
|
|
|
* _v1/2 had the problem "Why can't I write nameof(this.p)? and why is it so hard to write analyzers?"_
|
|
* _v3 had the problem "Why can't I write nameof(MyClass1.p)? and why is it so hard to name method-groups?"_
|
|
* _v4 had the problem "What name should be returned for types? and how exactly does it relate to member lookup?"_
|
|
|
|
This particular "nameof v5" proposal came from a combined VB + C# LDM on 2014.10.22. We went through the key decisions:
|
|
|
|
1. __Allow to dot an instance member off a type? Yes.__ Settled on the answer "yes", based on the evidence that v1/v2 had it and it worked nicely, and v3 lacked it and ended up with unacceptably ugly "default(T)" constructions.
|
|
2. __Allow to dot instance members off an instance? Yes.__ Settled on the answer "yes", based on the evidence that v1/v2 lacked it and it didn't work well enough when we used the CTP, primarily for the case "this.p"
|
|
3. __Allow to name method-groups? Yes.__ Settled on the answer "yes", based on the evidence that v1/v2 had it and it worked nicely, and v3 lacked it and ended up with unacceptably ugly constructions to select which method overload.
|
|
4. __Allow to unambiguously select a single overload? No.__ Settled on the answer "no" based on the evidence that v3 let you do this but it looked too confusing. I know people want it, and it would be a stepping stone to infoof, but at LDM we rejected these (good) reasons as not worth the pain. The pain is that the expressions look like they'll be executed, and it's unclear whether you're getting the nameof the method or the nameof the result of the invocation, and they're cumbersome to write.
|
|
5. __Allow to use nameof(other-nonexpression-types)? No.__ Settled on the answer "only nameof(expression)". I know people want other non-expression arguments, and v1/v2 had them, and it would be more elegant to just write nameof(List<>.Length) rather than having to specify a concrete type argument. But at LDM we rejected these (good) reasons as not worth the pain. The pain is that the language rules for member access in expressions are too different from those for member access in the argument to nameof(.), and indeed member access for StrongBox<>.Value.Length doesn't really exist. The effort to unify the two concepts of member access would be way disproportionate to the value of nameof. This principle, of sticking to existing language concepts, also explains why v5 uses the standard language notions of "member lookup", and hence you can't do nameof(x.y) to refer to both a method and a type of name "y" at the same time.
|
|
6. __Use source names or metadata names? Source.__ Settled on source names, based on the evidence... v1/v2/v3 were source names because that's how we started; then we heard feedback that people were interested in metadata names and v4 explored metadata names. But I think the experiment was a failure: it ended up looking like _disallowing_ nameof on types was better than picking metadata names. At LDM, after heated debate, we settled on source names. For instance `using foo=X.Y.Z; nameof(foo)` will return "foo". Also `string f<T>() => nameof(T)` will return "T". There are pros and cons to this decision. In its favor, it keeps the rules of nameof very simple and predictable. In the case of nameof(member), it would be a hindrance in most (not all) bread-and-butter cases to give a fully qualified member name. Also it's convention that "System.Type.Name" refers to an unqualified name. Therefore also nameof(type) should be unqualified. If ever you want a fully-qualified type name you can use typeof(). If you want to use nameof on a type but also get generic type parameters or arguments then you can construct them yourself, e.g. nameof(List\<int>) + "`2". Also the languages have no current notion of metadata name, and metadata name can change with obfuscation.
|
|
7. __Allow arbitrary expressions or just a subset?__ We want to try out the proposal "just a subset" because we're uneasy with full expressions. That's what v5 does. We haven't previously explored this avenue, and it deserves a try.
|
|
8. __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 to also have to deal with that in nameof. [this item 8 was added after the initial v5 spec]
|
|
|
|
|
|
I should say, we're not looking for unanimous consensus -- neither amongst the language design team nor amongst the codeplex OSS community! We hear and respect that some people would like something closer to infoof, or would make different tradeoffs, or have different use-cases. On the language design team we're stewards of VB/C#: we have a responsibility to listen to and understand _every opinion_, and then use our own judgment to weigh up the tradeoffs and use-cases. I'm glad we get to do language design in the open like this. We've all been reading the comments on codeplex and elsewhere, our opinions have been swayed, we've learned about new scenarios and issues, and we'll end up with a better language design thanks to the transparency and openness. I'm actually posting these notes on codeplex as my staging ground for sharing them with the rest of the team, so the codeplex audience really does see everything "in the raw".
|
|
|
|
|
|
# C# Syntax
|
|
```
|
|
expression: ... | nameof-expression
|
|
|
|
name-of-expression:
|
|
nameof ( expression )
|
|
```
|
|
In addition to the syntax indicated by the grammar, there are some additional syntactic constraints: (1) the argument expression can only be made up out of simple-name, member-access, base-access, or "this", and (2) cannot be simply "this" or "base" on its own. These constraints ensure that the argument looks like it has a name, and doesn't look like it will be evaluated or have side effects. I found it easier to write the constraints in prose than in the grammar.
|
|
|
|
[clarification update] Note that member-access has three forms, `E.I<A1...AK>` and `predefined-type.I<A1...AK>` and `qualified-alias-member.I`. All three forms are allowed, although the first case `E` must only be made out of allowed forms of expression.
|
|
|
|
If the argument to nameof at its top level has an unacceptable form of expression, then it gives the error "This expression does not have a name". If the argument contains an unacceptable form of expression deeper within itself, then it gives the error "This sub-expression cannot be used as an argument to nameof".
|
|
|
|
It is helpful to list some things not allowed as the nameof argument:
|
|
```
|
|
invocation-expression e(args)
|
|
assignment x += 15
|
|
query-expression from y in z select y
|
|
lambda-expression () => e
|
|
conditional-expression a ? b : c
|
|
null-coalescing-expression a?? b
|
|
binary-expression ||, &&, |, ^, &, ==, !=,
|
|
<, >, <=, >=, is, as, <<,
|
|
>>, +, -, *, /, %
|
|
prefix-expression +, -, !, ~, ++, --,
|
|
*, &, (T)e
|
|
postfix-expression ++, --
|
|
array-creation-expression new C[…]
|
|
object-creation-expression new C(…)
|
|
delegate-creation-expression new Action(…)
|
|
anonymous-object-creation-expression new {…}
|
|
typeof-expression typeof(int)
|
|
checked-expression checked(…)
|
|
unchecked-expression unchecked(…)
|
|
default-value-expression default(…)
|
|
anonymous-method-expression delegate {…}
|
|
pointer-member-access e->x
|
|
sizeof-expression sizeof(int)
|
|
literal "hello", 15
|
|
parenthesized-expression (x)
|
|
element-access e[i]
|
|
base-access-indexed base[i]
|
|
await-expression await e
|
|
nameof-expression nameof(e)
|
|
vb-dictionary-lookup e!foo
|
|
```
|
|
|
|
Note that there are some types which are not counted as expressions by the C# grammar. These are not allowed as nameof arguments (since the nameof syntax only allows expressions for its argument). There's no need to spell out that the following things are not valid expressions, since that's already said by the language syntax, but here's a selection of some of the types that are not expressions:
|
|
```
|
|
predefined-type int, bool, float, object,
|
|
dynamic, string
|
|
nullable-type Customer?
|
|
array-type Customer[,]
|
|
pointer-type Buffer*, void*
|
|
qualified-alias-member A::B
|
|
void void
|
|
unbound-type-name Dictionary<,>
|
|
```
|
|
|
|
|
|
# Semantics
|
|
|
|
The nameof expression is a constant. In all cases, nameof(...) is evaluated at compile-time to produce a string. Its argument is not evaluated at runtime, and is considered unreachable code (however it does not emit an "unreachable code" warning).
|
|
|
|
_Definite assignment._ The same rules of definite assignment apply to nameof arguments as they do to all other unreachable expressions.
|
|
|
|
_Name lookup_. In the following sections we will be discussing member lookup. This is discussed in $7.4 of the C# spec, and is left unspecified in VB. To understand nameof it is useful to know that the existing member lookup rules in both languages either return a single type, or a single instance/static field, or a single instance/static event, or a property-group consisting of overloaded instance/static properties of the same name (VB), or a method-group consisting of overloaded instance/static/extension methods of the same name. Or, lookup might fail either because no symbol was found or because ambiguous conflicting symbols were found that didn't fall within the above list of possibilities.
|
|
|
|
_Argument binding_. The nameof argument refers to one or more symbols as follows.
|
|
|
|
__nameof(simple-name)__, of the form I or I<A1...AK>
|
|
The normal rules of simple name lookup $7.6.2 are used but with one difference...
|
|
* The third bullet talks about member lookup of I in T with K type arguments. Its third sub-bullet says _"Otherwise, the result is the same as a member access of the form T.I or T.I<A1...AK>. In this case, it is a binding-time error for the simple-name to refer to an instance member."_ For the sake of nameof(simple-name), this case does not constitute a binding-time error.
|
|
|
|
|
|
__nameof(member-access)__, of the form E.I or E.I<A1...AK>
|
|
The normal rules of expression binding are used to evaluate "E", with _no changes_. After E has been evaluated, then E.I<A1...AK> is evaluated as per the normal rules of member access $7.6.4 but with some differences...
|
|
* The third bullet talks about member lookup of I in E. Its sub-bullets have rules for binding when I refers to static properties, fields and events. For the sake of nameof(member-access), each sub-bullet applies to instance properties, fields and events as well.
|
|
* The fourth bullet talks about member lookup of I in T. Its sub-bullets have rules for binding when I refers to instance properties, fields and events. For the sake of nameof(member-access), each sub-bullet applies to static properties, fields and events as well.
|
|
|
|
__nameof(base-access-named)__, of the form base.I or base.I<A1...AK>
|
|
This is treated as nameof(B.I) or nameof(B.I<A1...AK> where B is the base class of the class or struct in which the construct occurs.
|
|
|
|
|
|
_Result of nameof_. The result of nameof is the identifier "I" with the _standard identifier transformations_. Note that, at the top level, every possible argument of nameof has "I<A1...AK>".
|
|
|
|
[update that was added after the initial v5 spec] If "I" binds to a method-group and the argument of nameof has generic type arguments at the top level, then it produces an error "Do not use generic type arguments to specify the name of methods". Likewise for property-groups.
|
|
|
|
The standard identifier transformations in C# are detailed in $2.4.2 of the C# spec: 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. In VB, any surrounding [] is removed
|
|
|
|
# Implementation
|
|
In C#, nameof is stored in a normal InvocationExpressionSyntax node with a single argument. That is because in C# 'nameof' is a contextual keyword, which will only become the "nameof" operator if it doesn't already bind to a programmatic symbol named "nameof". TO BE DECIDED: what does its "Symbol" bind to?
|
|
|
|
In VB, NameOf is a reserved keyword. It therefore has its own node:
|
|
```vb
|
|
Class NameOfExpressionSyntax : Inherits ExpressionSyntax
|
|
Public ReadOnly Property Argument As ExpressionSyntax
|
|
End Class
|
|
```
|
|
TO BE DECIDED: Maybe VB should just be the same as C#. Or maybe C# should do the same as VB.
|
|
|
|
What is the return value from `var r = semanticModel.GetSymbolInfo(argument)`? In all cases, r.Candidates is the list of symbol. If there is only one symbol then it is in r.Symbol; otherwise r.Symbol is null and the reason is "ambiguity".
|
|
|
|
Analyzers and the IDE will just have to deal with this case.
|
|
|
|
|
|
# IDE behavior
|
|
```cs
|
|
class C {
|
|
[3 references] static void f(int i) {...nameof(f)...}
|
|
[3 references] void f(string s) {...nameof(this.f)...}
|
|
[3 references] void f(object o) {...nameof(C.f)...}
|
|
}
|
|
static class E {
|
|
[2 references] public static void f(this C c, double d) {}
|
|
}
|
|
```
|
|
|
|
__Highlight symbol from argument__: When you set your cursor on an argument to nameof, it highlights all symbols that the argument bound to. In the above examples, the simple name "nameof(f)" binds to the three members inside C. The two member access "nameof(this.f)" and "nameof(C.f)" both bind to extension members as well.
|
|
|
|
__Highlight symbol from declaration__: When you set your cursor on any declaration of f, it highlights all nameof arguments that bind to that declaration. Setting your cursor on the extension declaration will highlight only "this.f" and "C.f". Setting your cursor on any member of C will highlight both all three nameof arguments.
|
|
|
|
__Goto Definition__: When you right-click on an argument to nameof in the above code and do GoToDef, it pops up a FindAllReferences dialog to let you chose which declaration. (If the nameof argument bound to only one symbol then it would go straight to that without the FAR dialog.)
|
|
|
|
__Rename declaration__: If you do a rename-refactor on one of the declarations of f in the above code, the rename will only rename this declaration (and will not rename any of the nameof arguments); the rename dialog will show informational text warning you about this. If you do a rename-refactor on the _last remaining_ declaration of f, then the rename will also rename nameof arguments. Note that if you turn on the "Rename All Overloads" checkbox of rename-refactor, then it will end up renaming all arguments.
|
|
|
|
__Rename argument__: If you do a rename-refactor on one of the nameof arguments in the above code, the rename dialog will by default check the "Rename All Overloads" button.
|
|
|
|
__Expand-reduce__: The IDE is free to rename "nameof(p)" to "nameof(this.p)" if it needs to do so to remove ambiguity during a rename. This might make nameof now bind to more things than it used to...
|
|
|
|
__Codelens__: We've articulated the rules about what the argument of nameof binds to. The CodeLens reference counts above are a straightforward consequence of this.
|
|
|
|
|
|
## Bread and butter cases
|
|
```cs
|
|
// Validate parameters
|
|
void f(string s) {
|
|
if (s == null) throw new ArgumentNullException(nameof(s));
|
|
}
|
|
```
|
|
|
|
```cs
|
|
// MVC Action links
|
|
<%= Html.ActionLink("Sign up",
|
|
@typeof(UserController),
|
|
@nameof(UserController.SignUp))
|
|
%>
|
|
```
|
|
|
|
```cs
|
|
// INotifyPropertyChanged
|
|
int p {
|
|
get { return this._p; }
|
|
set { this._p = value; PropertyChanged(this, new PropertyChangedEventArgs(nameof(this.p)); }
|
|
}
|
|
// also allowed: just nameof(p)
|
|
```
|
|
|
|
```cs
|
|
// XAML dependency property
|
|
public static DependencyProperty AgeProperty = DependencyProperty.Register(nameof(Age), typeof(int), typeof(C));
|
|
```
|
|
|
|
```cs
|
|
// Logging
|
|
void f(int i) {
|
|
Log(nameof(f), "method entry");
|
|
}
|
|
```
|
|
|
|
```cs
|
|
// Attributes
|
|
[DebuggerDisplay("={" + nameof(getString) + "()}")]
|
|
class C {
|
|
string getString() { ... }
|
|
}
|
|
```
|
|
|
|
# Examples
|
|
|
|
```cs
|
|
void f(int x) {
|
|
nameof(x)
|
|
}
|
|
// result "x": Parameter (simple name lookup)
|
|
```
|
|
|
|
```cs
|
|
int x=2; nameof(x)
|
|
// result "x": Local (simple name lookup)
|
|
```
|
|
|
|
```cs
|
|
const x=2; nameof(x)
|
|
// result "x": Constant (simple name lookup)
|
|
```
|
|
|
|
```cs
|
|
class C {
|
|
int x;
|
|
... nameof(x)
|
|
}
|
|
// result "x": Member (simple name lookup)
|
|
```
|
|
|
|
```cs
|
|
class C {
|
|
void f() {}
|
|
nameof(f)
|
|
}
|
|
// result "f": Member (simple name lookup)
|
|
```
|
|
|
|
```cs
|
|
class C {
|
|
void f() {}
|
|
nameof(f())
|
|
}
|
|
// result error "This expression does not have a name"
|
|
```
|
|
|
|
```cs
|
|
class C {
|
|
void f(){}
|
|
void f(int i){}
|
|
nameof(f)
|
|
}
|
|
// result "f": Method-group (simple name lookup)
|
|
```
|
|
|
|
```cs
|
|
Customer c; ... nameof(c.Age)
|
|
// result "Age": Property (member access)
|
|
```
|
|
|
|
```cs
|
|
Customer c; ... nameof(c._Age)
|
|
// result error "_Age is inaccessible due to its protection level: member access
|
|
```
|
|
|
|
```cs
|
|
nameof(Tuple.Create)
|
|
// result "Create": method-group (member access)
|
|
```
|
|
|
|
```cs
|
|
nameof(System.Tuple)
|
|
// result "Tuple": Type (member access). This binds to the non-generic Tuple class; not to all of the Tuple classes.
|
|
```
|
|
|
|
```cs
|
|
nameof(System.Exception)
|
|
// result "Exception": Type (member access)
|
|
```
|
|
|
|
```cs
|
|
nameof(List<int>)
|
|
// result "List": Type (simple name lookup)
|
|
```
|
|
|
|
```cs
|
|
nameof(List<>)
|
|
// result error "type expected": Unbound types are not valid expressions
|
|
```
|
|
|
|
```cs
|
|
nameof(List<int>.Length)
|
|
// result "Length": Member (Member access)
|
|
```
|
|
|
|
```cs
|
|
nameof(default(List<int>))
|
|
// result error "This expression doesn't have a name": Not one of the allowed forms of nameof
|
|
```
|
|
|
|
```cs
|
|
nameof(default(List<int>).Length)
|
|
// result error "This expression cannot be used for nameof": default isn't one of the allowed forms
|
|
```
|
|
|
|
```cs
|
|
nameof(int)
|
|
// result error "Invalid expression term 'int'": Not an expression. Note that 'int' is a keyword, not a name.
|
|
```
|
|
|
|
```cs
|
|
nameof(System.Int32)
|
|
// result "Int32": Type (member access)
|
|
```
|
|
|
|
```cs
|
|
using foo=System.Int32;
|
|
nameof(foo)
|
|
// result "foo": Type (simple name lookup)
|
|
```
|
|
|
|
```cs
|
|
nameof(System.Globalization)
|
|
// result "Globalization": Namespace (member access)
|
|
```
|
|
|
|
```cs
|
|
nameof(x[2])
|
|
nameof("hello")
|
|
nameof(1+2)
|
|
// error "This expression does not have a name": Not one of the allowed forms of nameof
|
|
```
|
|
|
|
```vb
|
|
NameOf(a!Foo)
|
|
' error "This expression does not have a name": VB-specific. Not one of the allowed forms of NameOf.
|
|
```
|
|
|
|
```vb
|
|
NameOf(dict("Foo"))
|
|
' error "This expression does not have a name": VB-specific. This is a default property access, which is not one of the allowed forms.
|
|
```
|
|
|
|
```vb
|
|
NameOf(dict.Item("Foo"))
|
|
' error "This expression does not have a name": VB-specific. This is an index of a property, which is not one of the allowed forms.
|
|
```
|
|
|
|
```vb
|
|
NameOf(arr(2))
|
|
' error "This expression does not have a name": VB-specific. This is an array element index, which is not one of the allowed forms.
|
|
```
|
|
|
|
```vb
|
|
Dim x = Nothing
|
|
NameOf(x.ToString(2))
|
|
' error "This expression does not have a name": VB-specific. This resolves to .ToString()(2), which is not one of the allowed forms.
|
|
```
|
|
|
|
```vb
|
|
Dim o = Nothing
|
|
NameOf(o.Equals)
|
|
' result "Equals". Method-group. Warning "Access of static member of instance; instance will not be evaluated": VB-specific. VB allows access to static members off instances, but emits a warning.
|
|
```
|
|
|
|
```cs
|
|
[Foo(nameof(C))]
|
|
class C {}
|
|
// result "C": Nameof works fine in attributes, using the normal name lookup rules.
|
|
```
|
|
|
|
```cs
|
|
[Foo(nameof(D))]
|
|
class C { class D {} }
|
|
// result "D": Members of a class are in scope for attributes on that class
|
|
```
|
|
|
|
```cs
|
|
[Foo(nameof(f))]
|
|
class C { void f() {} }
|
|
// result "f": Members of a class are in scope for attributes on that class
|
|
```
|
|
|
|
```cs
|
|
[Foo(nameof(T))]
|
|
class C<T> {}
|
|
// result error "T is not defined": A class type parameter is not in scope in an attribute on that class
|
|
```
|
|
|
|
```cs
|
|
[Foo(nameof(T))] void f<T> { }
|
|
// result error "T not defined": A method type parameter is not in scope in an attribute on that method
|
|
```
|
|
|
|
```cs
|
|
void f([Attr(nameof(x))] int x) {}
|
|
// result error "x is not defined": A parameter is not in scope in an attribute on that parameter, or any parameter in the method
|
|
```
|
|
|
|
```vb
|
|
Function f()
|
|
nameof(f)
|
|
End Function
|
|
' result "f": VB-specific. This is resolved as an expression which binds to the implicit function return variable
|
|
```
|
|
|
|
```vb
|
|
NameOf(New)
|
|
' result error "this expression does not have a name": VB-specific. Not one of the allowed forms of nameof. Note that New is not a name; it is a keyword used for construction.
|
|
```
|
|
|
|
```vb
|
|
Class C
|
|
Dim x As Integer
|
|
Dim s As String = NameOf(x)
|
|
End Class
|
|
' result "x": Field (simple name lookup)
|
|
```
|
|
|
|
```cs
|
|
class C {
|
|
int x;
|
|
string s = nameof(x);
|
|
}
|
|
// result "x". Field (simple name lookup)
|
|
```
|
|
|
|
```cs
|
|
class C {
|
|
static int x;
|
|
string s = nameof(x);
|
|
}
|
|
// result "x". Field (simple name lookup)
|
|
```
|
|
|
|
```cs
|
|
class C {
|
|
int x;
|
|
string s = nameof(C.x);
|
|
}
|
|
// result "x". Member (member access)
|
|
```
|
|
|
|
```cs
|
|
class C {
|
|
int x;
|
|
string s = nameof(default(C).x);
|
|
}
|
|
// result error "This expression isn't allowed in a nameof argument" - default.
|
|
```
|
|
|
|
```cs
|
|
struct S {
|
|
int x;
|
|
S() {var s = nameof(x); ...}
|
|
}
|
|
// result "x": Field access (simple name lookup). Nameof argument is considered unreachable, and so this doesn't violate definite assignment.
|
|
```
|
|
|
|
```cs
|
|
int x; ... nameof(x); x=1;
|
|
// result "x": Local access (simple name lookup). Nameof argument is unreachable, and so this doesn't violate definite assignment.
|
|
```
|
|
|
|
```cs
|
|
int x; nameof(f(ref x));
|
|
// result error "this expression does not have a name".
|
|
```
|
|
|
|
```cs
|
|
var @int=5; nameof(@int)
|
|
// result "int": C#-specific. Local (simple name lookup). The leading @ is removed.
|
|
```
|
|
|
|
```cs
|
|
nameof(m\u200c\u0065)
|
|
// result "me": C#-specific. The Unicode escapes are first resolved, and the formatting character \u200c is removed.
|
|
```
|
|
|
|
```vb
|
|
Dim [Sub]=5 : NameOf([Sub])
|
|
' result "Sub": VB-specific. Local (simple name lookup). The surrounding [.] is removed.
|
|
```
|
|
|
|
```cs
|
|
class C {
|
|
class D {}
|
|
class D<T> {}
|
|
nameof(C.D)
|
|
}
|
|
// result "D" and binds to the non-generic form: member access only finds the type with the matching arity.
|
|
```
|
|
|
|
```cs
|
|
class C<T> where T:Exception {
|
|
... nameof(C<string>)
|
|
}
|
|
// result error: the type 'string' doesn't satisfy the constraints
|
|
```
|
|
|
|
# [String Interpolation for C#](http://1drv.ms/1tFUvbq) #
|
|
|
|
An *interpolated string* is a way to construct a value of type `String` (or `IFormattable`) by writing the text of the string along with expressions that will fill in "holes" in the string. The compiler constructs a format string and a sequence of fill-in values from the interpolated string.
|
|
|
|
When it is treated as a value of type `String`, it is a shorthand for an invocation of
|
|
|
|
```cs
|
|
String.Format(string format, params object args[])
|
|
```
|
|
|
|
When it is converted to the type `IFormattable`, the result of the string interpolation is an object that stores a compiler-constructed *format string* along with an array storing the evaluated expressions. The object's implementation of
|
|
|
|
```cs
|
|
IFormattable.ToString(string format, IFormatProvider formatProvider)
|
|
```
|
|
|
|
is an invocation of
|
|
|
|
```cs
|
|
String.Format(IFormatProviders provider, String format, params object args[])
|
|
```
|
|
|
|
By taking advantage of the conversion from an interpolated string expression to `IFormattable`, the user can cause the formatting to take place later in a selected locale. See the section `System.Runtime.CompilerServices.FormattedString` for details.
|
|
|
|
Note: the converted interpolated string may have more "holes" in the format string than there were interpolated expression holes in the interpolated string. That is because some characters (such as `"\{"` `"}"`) may be translated into a hole and a corresponding compiler-generated fill-in.
|
|
|
|
## Lexical Grammar ##
|
|
|
|
An interpolated string is treated initially as a token with the following lexical grammar:
|
|
|
|
```
|
|
interpolated-string:
|
|
$ " "
|
|
$ " interpolated-string-literal-characters "
|
|
|
|
interpolated-string-literal-characters:
|
|
interpolated-string-literal-part interpolated-string-literal-parts
|
|
interpolated-string-literal-part
|
|
|
|
interpolated-string-literal-part:
|
|
single-interpolated-string-literal-character
|
|
simple-escape-sequence
|
|
hexadecimal-escape-sequence
|
|
unicode-escape-sequence
|
|
interpolation
|
|
|
|
simple-escape-sequence: one of
|
|
\' \" \\ \0 \a \b \f \n \r \t \v \{ \}
|
|
|
|
single-interpolated-string-literal-character:
|
|
Any character except " (U+0022), \ (U+005C), { (U+007B) and new-line-character
|
|
|
|
interpolation:
|
|
{ interpolation-contents }
|
|
|
|
interpolation-contents:
|
|
balanced-text
|
|
balanced-text : interpolation-format
|
|
|
|
balanced-text:
|
|
balanced-text-part
|
|
balanced-text-part balanced-text
|
|
|
|
balanced-text-part
|
|
Any character except ", (, [, {, /, \ and new-line-character
|
|
( balanced-text )
|
|
{ balanced-text }
|
|
[ balanced-text ]
|
|
regular-string-literal
|
|
delimited-comment
|
|
unicode-escape-sequence
|
|
/ after-slash
|
|
|
|
after-slash
|
|
Any character except ", (, [, {, /, \, * and new-line-character
|
|
( balanced-text )
|
|
{ balanced-text }
|
|
[ balanced-text ]
|
|
regular-string-literal
|
|
* delimited-comment-text[opt] asterisks /
|
|
unicode-escape-sequence
|
|
|
|
interpolation-format:
|
|
regular-string-literal
|
|
literal-interpolation-format
|
|
|
|
literal-interpolation-format:
|
|
interpolation-format-part
|
|
interpolation-format-part literal-interpolation-format
|
|
|
|
interpolation-format-part
|
|
Any character except ", :, \, } and new-line-character
|
|
```
|
|
|
|
With the additional restriction that a *delimited-comment-text* that is a *balanced-text-part* may not contain a *new-line-character*.
|
|
|
|
This lexical grammar is ambiguous in that it allows a colon appearing in *interpolation-contents* to be considered part of the *balanced-text*, or as the separator between the *balanced-text* and the *interpolation-format*. This ambiguity is resolved by considering it to be a separator between the *balanced-text* and *interpolation-format*.
|
|
|
|
## Syntactic Grammar ##
|
|
|
|
An *interpolated-string* token is reclassified, and portions of it are reprocessed lexically and syntactically, during syntactic analysis as follows:
|
|
|
|
- If the *interpolated-string* contains no *interpolation*, then it is reclassified as a *regular-string-literal*.
|
|
- Otherwise
|
|
- the portion of the *interpolated-string* before the first *interpolation* is reclassified as an *interpolated-string-start* terminal;
|
|
- the portion of the *interpolated-string* after the last *interpolation* is reclassified as an *interpolated-string-end* terminal;
|
|
- the portion of the *interpolated-string* between one *interpolation* and another *interpolation* is reclassified as an *interpolated-string-mid* terminal;
|
|
- the *balanced-text* of each *interpolation-contents* is reprocessed according to the language's lexical grammar, yielding a sequence of terminals;
|
|
- the colon in each *interpolation-contents* that contains an *interpolation-format* is classified as a colon terminal;
|
|
- each *interpolation-format* is reclassified as a *regular-string-literal* terminal; and
|
|
- the resulting sequence of terminals undergoes syntactic analysis as an *interpolated-string-expression*.
|
|
|
|
```
|
|
expression:
|
|
interpolated-string-expression
|
|
|
|
interpolated-string-expression:
|
|
interpolated-string-start interpolations interpolated-string-end
|
|
|
|
interpolations:
|
|
single-interpolation
|
|
single-interpolation interpolated-string-mid interpolations
|
|
|
|
single-interpolation:
|
|
interpolation-start
|
|
interpolation-start : regular-string-literal
|
|
|
|
interpolation-start:
|
|
expression
|
|
expression , expression
|
|
```
|
|
|
|
## Semantics ##
|
|
|
|
An *interpolated-string-expression* has type `string`, but there is an implicit *conversion from expression* from an *interpolated-string-expression* to the type `System.IFormattable`. By the existing rules of the language (7.5.3.3 Better conversion from expression), the conversion to `string` is a better conversion from expression.
|
|
|
|
An *interpolated-string-expression* is translated into an intermediate *format string* and *object array* which capture the contents of the interpolated string using the semantics of [Composite Formatting](http://msdn.microsoft.com/en-us/library/txafckwd(v=vs.110).aspx "Composite Formatting"). If treated as a value of type `string`, the formatting is performed using `string.Format(string format, params object[] args)` or equivalent code. If it is converted to `System.IFormattable`, an object of type [`System.Runtime.CompilerServices.FormattedString`](#FormattedString) is constructed using the format string and argument array, and that object is the value of the *interpolated-string-expression*.
|
|
|
|
The format string is constructed of the literal portions of the *interpolated-string-start*, *interpolated-string-mid*, and *interpolated-string-end* portions of the expression, with special treatment for `{` and `}` characters (see [Notes](#Notes)).
|
|
|
|
**The evaluation order needs to be specified**.
|
|
|
|
**The definite assignment rules need to be specified**.
|
|
|
|
## single-interpolation Semantics ##
|
|
|
|
**This section should describe in detail the construction of a [*format item*](http://msdn.microsoft.com/en-us/library/txafckwd(v=vs.110).aspx) from a single-interpolation, and the corresponding element of the object array**.
|
|
|
|
If an *interpolation-start* has a comma and a second expression, the second expression must evaluate to a compile-time constant of type `int`, which is used as the [*alignment* of a *format item*](http://msdn.microsoft.com/en-us/library/txafckwd(v=vs.110).aspx).
|
|
|
|
If a *single-interpolation* has a colon and a *regular-string-literal*, then the string literal is used as the [*formatString* of a *format item*](http://msdn.microsoft.com/en-us/library/txafckwd(v=vs.110).aspx).
|
|
|
|
## Notes ##
|
|
|
|
The compiler is free to translate an interpolated string into a format string and object array where the number of objects in the object array is not the same as the number of interpolations in the *interpolated-string-expression*. In particular, the compiler may translate `{` and `}` characters into a fill-in in the format string and a corresponding string literal containing the character. For example, the interpolated string `$"\{ {n} \}"` may be translated to `String.Format("{0} {1} {2}", "{", n, "}")`.
|
|
|
|
The compiler is free to use any overload of `String.Format` in the translated code, as long as doing so preserves the semantics of calling `string.Format(string format, params object[] args)`.
|
|
|
|
## Examples ##
|
|
|
|
The interpolated string
|
|
```
|
|
$"{hello}, {world}!"
|
|
```
|
|
is translated to
|
|
```
|
|
String.Format("{0}, {1}!", hello, world)
|
|
```
|
|
|
|
The interpolated string
|
|
```
|
|
$"Name = {myName}, hours = {DateTime.Now:hh}"
|
|
```
|
|
is translated to
|
|
```
|
|
String.Format("Name = {0}, hours = {1:hh}", myName, DateTime.Now)
|
|
```
|
|
|
|
The interpolated string
|
|
```
|
|
$"\{{6234:D}\}"
|
|
```
|
|
is translated to
|
|
```
|
|
String.Format("{0}{1:D}{2}", "{", 6234, "}")
|
|
```
|
|
|
|
For example, if you want to format something in the invariant locale, you can do so using this helper method
|
|
|
|
```cs
|
|
public static string INV(IFormattable formattable)
|
|
{
|
|
return formattable.ToString(null, System.Globalization.CultureInfo.InvariantCulture);
|
|
}
|
|
```
|
|
|
|
and writing your interpolated strings this way
|
|
|
|
```cs
|
|
string coordinates = INV("longitude={longitude}; latitude={latitude}");
|
|
```
|
|
|
|
## System.Runtime.CompilerServices.FormattedString ##
|
|
|
|
The following platform class is used to translate an interpolated string to the type `System.IFormattable`.
|
|
|
|
```cs
|
|
namespace System.Runtime.CompilerServices
|
|
{
|
|
public class FormattedString : System.IFormattable
|
|
{
|
|
private readonly String format;
|
|
private readonly object[] args;
|
|
public FormattedString(String format, params object[] args)
|
|
{
|
|
this.format = format;
|
|
this.args = args;
|
|
}
|
|
string IFormattable.ToString(string ignored, IFormatProvider formatProvider)
|
|
{
|
|
return String.Format(formatProvider, format, args);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Issues ##
|
|
|
|
1. As specified, an interpolated string with no interpolations cannot be converted to `IFormattable` because it is a string literal. It should have such a conversion.
|
|
|