meetings/2021/LDM-2021-03-10.md
https://github.com/dotnet/csharplang/issues/140
https://github.com/dotnet/csharplang/issues/133
We started today by looking at these proposals again, examining the whole space to determine a priority for which to start implementing first. Last time we looked at this was 6 months ago, with mild preference for doing 133 first, and these issues remain the issues with the largest general support on the csharplang repo.
Broadly speaking, C# today has 2 types of properties, each lying on the extreme end of a spectrum of flexibility:
We also see 2 broad classes of problems that users would like to be addressed:
get or set. This includes things like INotifyPropertyChanged and logging. 140 has a solution for this, the field keyword._value_doNotUseOutsideOfProperty.
133 solves this by allowing fields to be scoped to properties.One important distinction we see between these problems is that the first one, solved by the field keyword, tends to be much more pervasive
than the second. This is true both within a single codebase (if the user is using INPC, that kills all autoprops in every view model), and in
number of requests for the feature in general.
field keywordThe field keyword, while seemingly simple, has some interesting design decisions that we will need to settle on:
var, where we allow null to be assigned
to the local, but warn when it is used unsafely. It seems possible that we could devise rules that work similarly for the field keyword
here.field
keyword can lead to some complicated scenarios, particularly if the user wants to have both custom getters and setters. We could look at a
modifier like auto to enable usage of the field keyword in the property body, or put a requirement that such properties must have either
an auto-implemented get or set, but both of those feel like limitations we're not fans of. Investigation will be needed into how much
complexity this will introduce for the compiler.
Property scoped fields are similarly simple on first glance, but have some questions to resolve:
Lazy<T> would be unusable, since it likely needs access to this. Related questions:
We also considered whether to think of this proposal "property scoped locals", instead of as fields. However, thinking of these as locals raises confusing questions about lifetimes of these members, whether attributes can be applied to them, and still leaves the above questions unresolved.
Finally on this topic, we looked at whether we should expand the set of property-scoped things to methods, classes, and other definitions. Currently, we don't see a huge need for this, but we can revisit later if the need presents itself.
In a switch up from the last time we looked at this, we lean heavily towards looking at the field keyword first. It's probably more work,
but resolves more cases and provides more benefit to the users who want it, as it actually reduces boilerplate rather than just moving
boilerplate.
We do like both of these proposals and want them both in the language, but will start by creating a detailed spec for LDM to review on 140.
Finally today, we looked at the proposal for parameterless constructors. This proposal needs to make sure that it can handle what the general .NET ecosystem can do with struct constructors today, including private struct constructors.
Initialization behavior is one major open question. Today, constructors must initialize all fields in a struct, but they can delegate to the
parameterless constructor to zero-initialize all fields if they choose to. If the user is defining the parameterless constructor themselves,
they can no longer do this. We could consider making the zero-initialization an implicit part of the parameterless constructor if not all fields
were definitely assigned, but this would create an inconsistency with how structs work in general. A related question arises with field
initializers when no parameterless constructor is present: if one field initializer is present, do all fields have to be initialized, or can
we infer zeroing them? We should also consider a warning for this = default in a parameterless constructor that has field initializers, as
that will overwrite the field initializers.
We also looked at warnings around when parameterless constructors are not called, namely everywhere that default is used instead of new.
This includes arrays, uninitialized fields, and others. One immediate comparison is to the compromises we made around nullable: we don't
warn when an array of a non-nullable reference type is created and then immediately dereferenced, so it seems contradictory that we'd add
warnings here. While non-defaultable structs are an interesting idea that LDM has looked at in the past, it is orthogonal to this feature
and will need a bunch more rules than we can put in here.
Next, we considered scenarios where new StructType() does not actually mean calling the constructor, such as default parameter values. This
is allowed today, but if we add parameterless constructors it will not actually call that constructor. Warning or erroring on such things is
a breaking change, but the potential harm from developers thinking a constructor is being called when it's not is likely greater than any
harm to users that would need to specify default instead of new. We should consider erroring when a parameterless constructor exists for
these cases, and having a warning wave to cover the cases when there isn't a parameterless constructor to help guide users to idiomatic patterns.
Finally, we looked at interactions with generic type arguments. The new() constraint required a public constructor, internal and private
do not work for this. We can block direct substitution for such type parameters, but indirect substitution would be possible as where T : struct
already implies new() today. We don't think that we can realistically block all where T : struct substitutions for structs with non-public
constructors, and we think that a warning for these cases is likely to produce far too many false positives to be useful. This may be a case
where we have to simply hold our noses and compromise on letting the runtime exception happen.
Overall, the spec is in good shape, and we'll get started on implementation. As it comes up in the implementation work, we'll work through open questions.