Skip to content

Latest commit

 

History

History
98 lines (67 loc) · 7.07 KB

LDM-2022-01-12.md

File metadata and controls

98 lines (67 loc) · 7.07 KB

C# Language Design Meeting for January 12th, 2022

Agenda

  1. Open questions for field
    1. Initializers for semi-auto properties
    2. Definite assignment for struct types
  2. Generic Math Operator Enhancements

Quote(s) of the Day

  • "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"

Discussion

Open questions for field

https://github.com/dotnet/csharplang/blob/4773c1bdb692aba574204a9affa890b0af3d2c05/proposals/semi-auto-properties.md#open-ldm-questions

Initializers for semi-auto properties

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:

  1. Treat semi-auto properties as if they are manually-implemented properties. This means they would not support initializers.
  2. 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.
  3. 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.

Conclusion

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.

Definite assignment for struct types

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:

  1. 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 the this = default; assignment.
  2. 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.

Conclusion

No answers today.

Generic Math Operator Enhancements

#4665
#4666
#4682

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.

Conclusion

These proposals are generally approved of. We'll look forward to reviewing the concrete designs soon, particularly around checked.