meetings/2022/LDM-2022-10-17.md
https://github.com/dotnet/csharplang/issues/2691
As indicated during triage, the proposal for primary constructors has been updated with the latest thinking on the scenario, and we are now reviewing it. The new version uses capturing semantics for primary constructor parameters, and works for all types, not just records. In going over the proposal, we had a few comments:
field specifically needs to deal with backwards compatibility, while
primary constructors are all new. We can simply say that primary constructor parameters are always treated as captures for these types of purposes, and the
removal of the parameters is an optimization strategy the compiler is free to use or not as it chooses.
class C(int i)
{
protected int i = i; // References parameter
public int I => i; // References field
}
readonly modifier on a struct will impact the primary constructor.{ statements } syntax as a primary constructor body is not particularly liked. There are two leading alternatives:
ClassName { } - IE, just like a regular constructor, but minus the parentheses.init { } - More visually different from a constructor, which helps it stand out as being part of the primary constructor.Overall, we like the direction this proposal has taken, and would like to start playing with it. We're particularly interested in feedback on the double-capture issue, so a preview of the feature that users can try will help inform the capturing decision here.
We will proceed with a prototype with the proposed semantics, and a warning for the double-capture problem.
https://github.com/dotnet/csharplang/issues/4018
We took another look at updates to this proposal now that we have non-C# 11 time. The main thing we ended up debating was how "exact" should we make variable redeclaration need to be, as some edge cases can be hurt depending on our decisions. Some examples are:
// Example A
if (e is C c || e is Wrapper { NullableReferenceTypeProp: var c }) { } // Should there be an error since `var` is `C?` here
// Example B
if (e is S s || e is Wrapper { NullableValueTypeProp: var s }) { } // Should there be an error since this is S vs Nullable<S>?
// Example C
if (e is C<(int a, int b)> c || e is Wrapper { Prop: var c }) { c.TupleProperty.a } // Is this ok? Where does the name a come from in the second example?
// Example D
if (e is Derived d) { d.DerivedMethod(); }
else if (e is Wrapper { BaseTypeProp: Base d }) { } // Should d be widened to `Base`? What happens to the above method access?
We arrived at 4 possible options for the behavior:
var: var doesn't contribute type information when being unified across multiple declarations.In discussion, we started with 4. However, we were dissatisfied with the behavior of example D above with that rule, as it either means that a related case or
else if can affect the meaning of the code inside the first if block, or it means that we need to introduce variables that change their type based on the
current execution flow. We felt that 3 wasn't particularly clear on the behavior, and 2 was potentially confusing: what if there are multiple sets of tuple names?
How should we decide which names to use? This was less strong, however, and we are open to allowing differences in the future. Ultimately, we felt that variant 1b
is the best option, and we will look for user feedback to see if we should loosen the rules more.
Variant 1b of the rules accepted.