# C# Language Design Meeting for March 10th, 2021

## Agenda

1. [Property improvements](#property-improvements)
    1. [`field` keyword](#field-keyword)
    2. [Property scoped fields](#property-scoped-fields)
2. [Parameterless struct constructors](#parameterless-struct-constructors)

## Quote of the Day

- "I have a request for the second part of this meeting... more funny quotes!" "I'm here, don't worry." "If nothing
else happens I'll take it I guess."

## Discussion

### Property improvements

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](../2020/LDM-2020-06-24.md#property-enhancements) 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:

* Auto-props. These have no flexibility: a backing field of the exact same type as the property is declared, and can be read from/assigned
to only via the property getter and setter.
* Manual props. These are maximally flexible, but have a lot of boilerplate involved and any needed backing fields are exposed to the entire
class.

We also see 2 broad classes of problems that users would like to be addressed:

* Users would like to decorate the property. They still want a backing field of the same type as the property, but want to do some action on
`get` or `set`. This includes things like INotifyPropertyChanged and logging. 140 has a solution for this, the `field` keyword.
* Users would like to have either multiple pieces of state for one property, or have state that has a different type than the property itself.
Today, these pieces of state must be exposed to the entire type, which results in naming conventions like `_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` keyword

The field keyword, while seemingly simple, has some interesting design decisions that we will need to settle on:

* What should the nullability of these backing fields be? An obvious answer is "should match the property", but this likely precludes the
ability to do any kind of lazy init in such properties. Rather, this scenario seems similar to `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.
* There are some concerns about how the compiler will know whether the property is a partial auto-prop or not. Just using the `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.
    * Another important concern here is that it's not just the compiler that will need to understand this: users reading will need to do the
    same determination.

#### Property scoped fields

Property scoped fields are similarly simple on first glance, but have some questions to resolve:

* Are these _truly_ only visible in the property? Or can they be seen from the constructor? If they're not visible in the constructor, then
it seems like that restriction would mean `Lazy<T>` would be unusable, since it likely needs access to `this`. Related questions:
    * If they are visible, how can they be accessed? Just by name? Dotted off the property somehow?
    * Can names conflict with each other?
    * Can these property-scoped fields be seen by overrides? What are the valid accessibilities for them?
* How do these fields influence nullable analysis?

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.

#### Conclusions

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.

### Parameterless struct constructors

https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/parameterless-struct-constructors.md

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.

#### Conclusions

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.
