- "I should really get a beer, but I'll wait" "Stop [redacted], it's only 10:01 here" ... "What time is it there, snark-o'clock?" "It would be if I grabbed that beer"
- "It was stupid syntax, but it was legal"
For semi-auto properties, there is a question of where initializers are allowed, and what effect they have when written. Our existing rules are pretty simple:
manually-implemented properties cannot have initializers. Auto-properties can have initializers, and the effect of the initializer is assigning to the backing field.
The field
keyword walks a fine line here, sitting squarely between both worlds. We think there's some main issues to solve:
- If there is a manually-implemented setter, then an initializer cannot call the setter, as initializers run before the constructor and setters can reference type state. Initializers cannot reference type state, so this breaks the rules that rely on that invariant.
- On the other hand, for manually-implemented setters, if an initializer assigned directly to the backing field this would cause a very visible but non-obvious behavior difference between putting an initializer on a property and an initializer in the constructor. It is possible to observe a difference here today, but it required a virtual property overridden in a dervied type and is a much more niche case. We think this difference is both confusing and a potential footgun in waiting.
To solve this, we have a few options for restrictions on initializers:
- Treat semi-auto properties as if they are manually-implemented properties. This means they would not support initializers.
- Treat semi-auto properties with non-existent for auto-implemented setters as if they were auto properties. This means such properties would be allowed to have initializers, and no other types of properties would be allowed to do so.
- Allow all properties with backing fields to have initializers. These would assign to the backing field, and there would be a meaningful difference between the initializer and assigning in the constructor.
After some discussion, we feel that 3 is a step too far. It could potentially be enabled later, if we can find a set of rules that makes sense and unifies the area, but for the moment we don't have those rules. We also think 1 might be a bit too conservative: part of this feature is easing the cliff between full properties and auto properties. Disallowing initializers when the setter is "trivial" seems like it goes against this desire.
There's also some potential future overlap with property-scoped fields. These fields will, presumably, need to be
accessible in a limited fashion from constructors or other locations that can access type state, in order to initialize them. Lazy<T>
backing fields, for example,
will almost certainly need to be initialized with a delegate that references the current type, something that cannot be done from an initializer today. If we need
more granular control for the automatic backing field, we think we can rely on that proposal to allow this control.
We have the following formalized rule for when initializers are permitted:
- Initializers are permitted when a property has a backing field that will be emitted and the property either does not have a setter, or its setter is auto-implemented.
We have the following rule for when properties can be assigned in the constructor of a type:
- Properties can be assigned in the constructor of a type if that property has a setter, or is a get-only property whose backing field will be emitted.
Semi-auto properties pose a very interesting challenge for definite assignment in struct types. Today, all fields in a struct type must be fully initialized by any
constructor of that type. Existing auto properties are fine here: the compiler controls the implementation of the set
method, and we know that they are not accessing
any instance state other than the backing field, and not exposing this
before assignment of all fields are completed. Semi-auto properties, by contrast, can have a
user-implemented set
or init
method, which has all the power a regular set
or init
method has. It can access instance state, potentially before some other part
of the instance state has been assigned. Further, there is no way to mention the backing field from outside the property (this being one of the main selling points of
the feature). We see a few potential solutions for this:
- Require a chain to another constructor or a
this = default
before access to semi-auto properties is permitted. This a bitter pill, as property initializers run before the constructor body. This would mean any property initializers are blown away by thethis = default;
assignment. - Have the compiler initialize all automatically-generated backing fields to
default
. This would allow skipping initialization of all auto and semi-auto properties, the former of which is not allowed today. While we think this would work, it introduces inconsistencies between regular fields and backing fields, which is odd, and there could be some clients that suffer from default initializers for all backing fields (mainly around perf).
After some discussion, we're pretty conflicted here. We'll have a small group go and explore these ideas and other potential solutions and come back to the LDM with their findings.
No answers today.
Finally, we took a look at 3 different operator-related enhancements for the generic math scenarios we're targeting for .NET 7/C# 11. We're supportive of all of them,
but the checked
operator enhancements are definitely going to be the largest amount of work of the 3. There's new syntax and rules around what things are called when
to be hammered out.
For relaxing shift operator requirements, we think that this rule has served its purpose. It is technically possible, via external libraries, to emulate cout
and other
shift operator semantics in C# today, but we don't see people doing this. Relaxing the restrictions will make it easier for those libraries to be written, but the general
patterns for the language have been established at this point.
These proposals are generally approved of. We'll look forward to reviewing the concrete designs soon, particularly around checked
.