Add notes for 2013-11-04
This commit is contained in:
Родитель
21dc9561ae
Коммит
150100658d
|
@ -0,0 +1,101 @@
|
|||
# C# Design Notes for Nov 4, 2013
|
||||
## Agenda
|
||||
Next up for implementation are the features for auto-properties and function bodies, both of which go especially well with primary constructors. For the purpose of spec’ing those, we nailed down a few remaining details. We also took another round discussing member access in the lightweight dynamic scenario.
|
||||
1. Initialized and getter-only auto-properties <details decided>
|
||||
2. Expression-bodied function members <details decided>
|
||||
3. Lightweight dynamic <member access model and syntax discussed>
|
||||
|
||||
## Initialized and getter-only auto-properties
|
||||
We are allowing initializers for auto-properties, and we are allowing getter-only auto-properties, where the underlying field is untouched after the initializer has run. This was last discussed on May 20, and allows declarations like this:
|
||||
``` c#
|
||||
public bool IsActive { get; } = true;
|
||||
```
|
||||
In order to spec these feature additions there are a couple of questions to iron out.
|
||||
## Getter-only auto-properties without initializers
|
||||
Initializers will be optional on get-set auto-properties. Should we allow them to be left out on getter-only properties?
|
||||
``` c#
|
||||
public bool IsActive { get; } // No way to get a non-default value in there
|
||||
```
|
||||
There’s a symmetry and simplicity argument that we should allow this. However, anyone writing it is probably making a mistake, and even if they really meant it, they (and whoever inherits their code) are probably better off explicitly initializing to the default value.
|
||||
### Conclusion
|
||||
We’ll disallow this.
|
||||
### Is the backing field of getter-only auto-properties read-only?
|
||||
There’s an argument for fewer differences with get-set auto-properties. However, the field is really never going to change, and any tool that works on the underlying field (e.g. at the IL level) would benefit from seeing that it is indeed readonly.
|
||||
|
||||
Readonly does not prevent setting through reflection, so deserialization wouldn’t be affected.
|
||||
### Conclusion
|
||||
Let’s make them readonly. There’s a discrepancy with VB’s decision here, which should be rationalized.
|
||||
## Expression-bodied function members
|
||||
|
||||
This feature would allow the body of methods and of getter-only properties to be expressed very concisely with a single expression. This feature was last discussed on April 15, and would allow code like this:
|
||||
``` c#
|
||||
public class Point(int x, int y) {
|
||||
public int X => x;
|
||||
public int Y => y;
|
||||
public double Dist => Math.Sqrt(x * x + y * y);
|
||||
public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
|
||||
}
|
||||
```
|
||||
Again, there are a few details to iron out.
|
||||
### Are we happy with the syntax for expression-bodied properties?
|
||||
The syntax is so terse that it may not be obvious to the reader that it is a property at all. It is just one character off from a field with an initializer, and lacks the telltale get and/or set keywords.
|
||||
|
||||
One could imagine a slightly more verbose syntax:
|
||||
``` c#
|
||||
public int X { get => x; }
|
||||
```
|
||||
This doesn’t read quite as well, and gives less of an advantage in terms of terseness.
|
||||
### Conclusion
|
||||
We’ll stick with the terse syntax, but be open to feedback if people get confused.
|
||||
## Which members can have expression bodies?
|
||||
While methods and properties are clearly the most common uses, we can imagine allowing expression bodies on other kinds of function members. For each kind, here are our thoughts.
|
||||
### Operators: Yes
|
||||
Operators are like static methods. Since their implementations are frequently short and always have a result, it makes perfect sense to allow them to have expression bodies:
|
||||
``` c#
|
||||
public static Complex operator +(Complex a, Complex b) => a.Add(b);
|
||||
```
|
||||
### User defined conversions: Yes
|
||||
Conversions are just a form of operators:
|
||||
``` c#
|
||||
public static implicit operator string(Name n) => n.First + " " + n.Last;
|
||||
```
|
||||
### Indexers: Yes
|
||||
Indexers are like properties, except with parameters:
|
||||
``` c#
|
||||
public Customer this[Id id] => store.LookupCustomer(id);
|
||||
```
|
||||
### Constructors: No
|
||||
Constructors have syntactic elements in the header in the form of this(…) or base(…) initializers which would look strange just before a fat arrow. More importantly, constructors are almost always side-effecting statements, and don’t return a value.
|
||||
### Events: No
|
||||
Events have add and remove accessors. Both must be specified, and both would be side-effecting, not value returning. Not a good fit.
|
||||
### Finalizers: No
|
||||
Finalizers are side effecting, not value returning.
|
||||
### Conclusion
|
||||
To summarize, expression bodies are allowed on methods and user defined operators (including conversions), where they express the value returned from the function, and on properties and indexers where they express the value returned from the getter, and imply the absence of a setter.
|
||||
### void-returning methods
|
||||
Methods returning void, and async methods returning task, do not have a value-producing return statement. In that respect they are like some of the member kinds we rejected above. Should we allow them all the same?
|
||||
### Conclusion
|
||||
We will allow these methods to have expression bodies. The rule is similar to expression-bodied lambdas: the body has to be a statement expression; i.e. one that is “certified” to be executed for the side effect.
|
||||
## Lightweight dynamic member access
|
||||
At the design meeting on Oct 21, we discussed options for implementing “user-defined” member access. There are two main directions:
|
||||
- Allow users to override the meaning of dot
|
||||
- Introduce a “dot-like” syntax for invoking string indexers
|
||||
|
||||
The former really has too many problems, the main one being what to do with “real” dot. Either we make “.” retain is current meaning and only delegate to the user-supplied meaning when that fails. But that means the new dot does not get a full domain of names. Or we introduce an escape hatch syntax for “real dot”. But then people would start using that defensively everywhere, especially in generated code. It also violates substitutability where o.x suddenly means something else on a subtype than a supertype.
|
||||
|
||||
We strongly prefer the latter option, providing a “dot-like” syntax that translates into simply invoking a string indexer with the provided identifier as a string. This has the added benefit of working with existing types that have string indexers. The big question of course is what dot-like syntax. Here are some candidates we looked at in order to home in on a design philosophy:
|
||||
``` c#
|
||||
x!Foo // Has a dot, but not as a separate character
|
||||
x."Foo" // That looks terrible, too much like any value could be supplied
|
||||
x.[Foo] // Doesn’t feel like Foo is a member name
|
||||
x.#Foo // Not bad
|
||||
x.+Foo // Hmmmm
|
||||
```
|
||||
Some principles emerge from this exercise:
|
||||
- Using "…" or […] kills the illusion of it being a member name
|
||||
- Having characters after the identifier breaks the flow
|
||||
- Using a real dot feels right, and would help with the tooling experience
|
||||
- What comes after that real dot really matters!
|
||||
|
||||
### Conclusion
|
||||
We like the “dot-like” syntax approach to LW dynamic member access, and we think it should be of the form x.<glyph>Foo for some value of <glyph>.
|
Загрузка…
Ссылка в новой задаче