meetings/2014/LDM-2014-02-03.md
We iterated on some of the features currently under implementation
Primary constructors as currently designed and implemented lead to automatic capture of parameters into private, compiler-generated fields of the object whenever those parameters are used after initialization time.
It is becoming increasingly clear that this is quite a dangerous design. To illustrate, what’s wrong with this code?
public class Point(int x, int y)
{
public int X { get; set; } = x;
public int Y { get; set; } = y;
public double Dist => Math.Sqrt(x * x + y * y);
public void Move(int dx, int dy)
{
x += dx; y += dy;
}
}
This appears quite benign, but is in fact catastrophically wrong. The use of x and y in Dist and Move causes these values to be captured as private fields. The auto-properties X and Y each cause their own backing fields to be generated, initialized with the x and y values passed in to the primary constructors. But from then on, X and Y lead completely distinct lives from x and y. Assignments to the X and Y properties will cause them to be observably updated, but the value of Dist remains unchanged. Conversely, changes through the Move method will reflect in the value of Dist, but not affect the value of the properties.
The way for the developer to avoid this is to be extremely disciplined about not referencing x and y except in initialization code. But that is like giving them a gun already pointing at their foot: sooner or later it will go subtly wrong, and they will have hard to find bugs.
There are other incarnations of this problem, e.g. where the parameter is passed to the base class and captured multiple times.
There are also other problems with implicit capture: we find, especially from MVP feedback, that people quickly want to specify certain things about the generated fields, such as readonly-ness, attributes, etc. We could allow those on the parameters, but they quickly don’t look like parameters anymore.
The best way for us to deal with this is to simply disallow automatic capture. The above code would be disallowed, and given the same declarations of x, y, X and Y, Dist and Move would have to written in terms of the properties:
public double Dist => Math.Sqrt(X * X + Y * Y);
public void Move(int dx, int dy)
{
X += dx; Y += dy;
}
Now this raises a new problem. What if you want to capture a constructor parameter in a private field and have no intention of exposing it publicly. You can do that explicitly:
public class Person(string first, string last)
{
private string _first = first;
private string _last = last;
public string Name => _first + " " + _last;
}
The problem is that the “good” lower case names in the class-level declaration space are already taken by the parameters, and the privates are left with (what many would consider) less attractive naming options.
We could address this in two ways (that we can think of) in the primary constructor feature:
The former option seems mysterious: two potentially quite different entities get to timeshare on the same name? And then you’d get confusing initialization code like this:
private string first = first; // WHAT???
private string last = last;
It seems that the latter option is the better one. We would allow field-like syntax to occur in a parameter list, which is a little odd, but kind of says what it means. Specifically specifying an accessibility on a parameter (typically private) would be what triggers capture as a field:
public class Person(private string first, private string last)
{
public string Name => _first + " " + _last;
}
Once there’s an accessibility specified, we would also allow other field modifiers on the parameter; readonly probably being the most common. Attributes could be applied to the field in the same manner as with auto-properties: through a field target.
Conclusion We like option two. Let’s add syntax for capture and not do it implicitly.
Grammar for indexed names
For the lightweight dynamic features, we’ve been working with a concept of “pseudo-member” or indexed name for the $identifier notation.
We will introduce this as a non-terminal in the grammar, so that the concept is reified. However, for the constructs that use it (as well as ordinary identifiers) we will create separate productions, rather than unify indexed names and identifiers under a common grammatical category.
For the stand-alone dictionary initializer notation of [expression] we will not introduce a non-terminal.
Nailing down the design of the null-propagating operator we need to decide a few things:
The main usage of course is with dot, as in x?.y and x?.m(…). It also potentially makes sense for element access x?[…] and invocation x?(…). And we also have to consider interaction with indexed names, as in x?.$y.
We’ll do element access and indexed member access, but not invocation. The former two make sense in the context that lightweight dynamic is addressing. Invocation seems borderline ambiguous from a syntactic standpoint, and for delegates you can always get to it by explicitly calling Invoke, as in d?.Invoke(…).
The semantics are like applying the ternary operator to a null equality check, a null literal and a non-question-marked application of the operator, except that the expression is evaluated only once:
e?.m(…) => ((e == null) ? null : e0.m(…))
e?.x => ((e == null) ? null : e0.x)
e?.$x => ((e == null) ? null : e0.$x)
e?[…] => ((e == null) ? null : e0[…])
Where e0 is the same as e, except if e is of a nullable value type, in which case e0 is e.Value.
The type of the result depends on the type T of the right hand side of the underlying operator:
T is (known to be) a reference type, the type of the expression is TT is (known to be) a non-nullable value type, the type of the expression is T?T is (known to be) a nullable value type, the type of the expression is TT is a reference or value type) the expression is a compile time error.