csharplang/meetings/2013/LDM-2013-11-04.md

6.9 KiB
Исходник Постоянная ссылка Ответственный История

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 specing 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:

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?

public bool IsActive { get; } // No way to get a non-default value in there

Theres 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

Well disallow this.

Is the backing field of getter-only auto-properties read-only?

Theres 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 wouldnt be affected.

Conclusion

Lets make them readonly. Theres a discrepancy with VBs 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:

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:

public int X { get => x; }

This doesnt read quite as well, and gives less of an advantage in terms of terseness.

Conclusion

Well 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:

public static Complex operator +(Complex a, Complex b) => a.Add(b);

User defined conversions: Yes

Conversions are just a form of operators:

public static implicit operator string(Name n) => n.First + " " + n.Last;

Indexers: Yes

Indexers are like properties, except with parameters:

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 dont 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:

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] // Doesnt 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>.