This project is read-only.

C# Design Notes for Nov 4, 2013

Topics: C# Language Design
Mar 29, 2014 at 5:28 PM

C# Design Notes for Nov 4, 2013

Notes are archived here.

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:
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
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:
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 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:
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 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:
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>.
Apr 3, 2014 at 9:07 PM
I think the glyph should be ..

Thus:
    a.b..c    ⇒    ((dynamic) (a.b)).c
This is extensible because the dot can now also be used to dyamicize indexing and invocation:
    a.[0]    ⇒    ((dynamic) a)[0]
    a.(x)    ⇒    ((dynamic) a)(x)
Apr 6, 2014 at 1:46 PM
I don't really like the proposal for expression-bodies function members. This breaks with the brace-style tradition of C# at very little benefit and just clutters up the syntax.
Apr 7, 2014 at 10:24 AM
The use case for "lightweight dynamic member access" seems to be "being able to refactor string indexes that happen to be valid C# identifiers easily".
We can already do that:
private const string Status = "Status";

// ...

var json = /*...*/;
string value = json[Status];
Is that feature really worth its cost? Adding a new special character to C# is a pretty big change, doing so for a special case of a special case of an already existing feature seems like a bad idea. But then I'm no language designer... is there something I'm missing?
Apr 7, 2014 at 10:54 AM
SolalPirelli has a point here. Why not use json.[Status] instead of json.$Status? No new special character, so it seems more aligned with the rest of C#.
Apr 7, 2014 at 3:23 PM
Is there a compelling reason for not allowing a getter-only auto-property to be assigned from within the body of a standard constructor (with the generated IL being simply an assignment to the backing field)?
public class Foo
{
    public Foo(Bar bar)
    {
        Guard.IsNotNull(bar);   
        this.Bar = bar;
    }
    
    public Bar Bar { get; }
}
This would allow enable validation, thereby making the read-only auto-properties much more generally useful.
Apr 7, 2014 at 6:12 PM
Another point against the "lightweight dynamic" syntax. Given the following code:
var obj1 = ...;
var obj2 = ...;

var prop1 = obj1.$Property;
var prop2 = obj2.$Property;
If I rename the first $Property, the IDE cannot know whether it should rename the second instance or not. It gets worse when the uses are in different locations, e.g. two classes each contain code that accesses $Property on some object. If the code used constant string indexers instead, there'd be no problem because there cannot be ambiguities in the index references (otherwise the code wouldn't compile).
This considerably weakens the "easy refactoring" advantage IMHO.

 
Is there a compelling reason for not allowing a getter-only auto-property to be assigned from within the body of a standard constructor (with the generated IL being simply an assignment to the backing field)?
That looks a bit weird... a property with only a getter that can still be set... I think re-using readonly is better:
public Foo MyFoo { get; readonly set; }
Not sure how that would look when combined with the new constructor syntax, though.
Apr 7, 2014 at 7:58 PM
Would
var obj = new MyType();
var prop = obj.$something;
work if indexer on MyType is declared as follows:
public int this[MyOtherType x]
{
    get
    {
        // (...)
    }
}
and implicit conversion from string to MyOtherType exists?
Apr 7, 2014 at 10:50 PM
Yes, X.$Y is merely shorthand for X["Y"].
Apr 8, 2014 at 10:11 AM
Yes, X.$Y is merely shorthand for X["Y"].
I see. I completely misunderstood the feature then. I thought X.$Y was going to be shorthand for ((dynamic) X).Y. Since it’s not that, the name Lightweight dynamic member access may need a rethink, because it’s massively misleading...
Apr 10, 2014 at 1:11 AM
X can be dynamic (in such case it would result in dynamic indexing), but does not have to.

Anything with a string indexer can be used with a member access - like syntax. As a trivial example - instance of Dictionary<string, int> can be used as if members of type int can be dynamically added to it, or removed. Lightweight part here is that you do not need to involve reflection or expression trees.
Apr 10, 2014 at 3:19 AM
I can't disagree with the decision to require an initializer on a read only auto property more. You came to the correct conclusion about making the backing field read only, but you should be able to sets its value in the same scope as a read only field then. (Ala Constructor) If I want to set the property to a derived value, how can I do it if an initializer is required.

(This is just my opinion)
Apr 14, 2014 at 7:24 PM
The term "lightweight dynamic" is in the design notes because when we had that meeting we hadn't yet landed on the term "indexed members". I decided to copy the design notes faithfully here, even when our thinking has changed since the meeting. Hopefully the benefit of giving insight into our design process outweighs the inconvenience of there being "outdated" stuff in there!
Apr 14, 2014 at 8:03 PM
@The C# team : Could we please get some insight into the reason you designed the .$ operator in the first place? As others (and myself) have said previously, it seems like a special-case feature that doesn't even cover all uses (e.g. strings that are not valid C# identifiers)... but then I'm no language designer, so I assume there must be something I'm not seeing.
Apr 15, 2014 at 12:30 AM
@SolalPirelli: I posted a little more explanation here: https://roslyn.codeplex.com/discussions/541566

If you are interested in the history of how we got there, I hope to get around to posting design notes going further back. But I don't think that's what you're asking for.
Aug 24, 2014 at 7:21 AM
Regarding the terseness of auto-properties with expression-bodied getters, how about the following syntax:
public class LengthTracker<T>(List<T> list) {
    public int OriginalLength { get; } =  list.Length; // evaluates up-front and never reevaluates
    public int CurrentLength  { get; } => list.Length; // reevaluates every time
}
This way it's obviously a property and still relatively terse. The only difference is whether you used the assignment operator (=) or the lambda operator (=>).

Also, I'm assuming this is only allowed on getter-only auto-properties specifically.
Aug 24, 2014 at 7:50 AM
Just read over the initializer scope notes, and I have one more suggestion. It stems to some extent from the closeness in syntax in the above two cases, but mainly from the fact that traditionally lambda expressions are known to "close over" variables, and given that expression bodies are syntactically delineated with the same operator as lambda expressions (namely =>), a bit of closure behavior wouldn't be surprising (indeed, I would imagine it to be expected).

Basically, the code above requires the list constructor parameter to be in scope of the CurrentLength property's expression-bodied getter, but according to the rules for the initializer scope, this wouldn't be the case. I would have to first assign it to a field of the same name, then in the expression body it would count as a reference to the field. But then it would seem odd that in one case it refers to the constructor parameter, whereas in the other case it refers to a field...

Taking this one step further, if we allow property expression bodies to close over the initializer scope, then one should expect all expression bodies to close over the initializer scope, even for methods:
public class LengthTracker<T>(List<T> list) {
    public int OriginalLength { get; } =  list.Length;
    public int CurrentLength  { get; } => list.Length;

    public string ToString() => Jsonify(list);
}
Aug 24, 2014 at 3:09 PM
gzak wrote:
public class LengthTracker<T>(List<T> list) {
    public int OriginalLength { get; } =  list.Length;
    public int CurrentLength  { get; } => list.Length;

    public string ToString() => Jsonify(list);
}
Most of that is already possible if you change the code like this:
public class LengthTracker<T>(List<T> list) {
     private List<T> _list = list;
     public int OriginalLength { get; } = list.Length;
     public int CurrentLength => _list.Length;
 
     public override string ToString() => Jsonify(_list);
}
What you additionally need would be implicit field capture (closure style). Primary constructor initially had explicit field capture but this feature got withdrawn.