csharplang/meetings/2020/LDM-2020-04-20.md
2020-05-15 08:24:07 -07:00

3.4 KiB

C# Language Design Meeting for April 20, 2020

Agenda

Records:

  1. Factory methods

Discussion

The proposal at its core is to allow certain methods to opt-in to certain language semantics that are only currently valid at construction, namely object initializers, collection initializers, and the new with expression (although that expression is legal on only certain factory methods).

Possible extension of the feature: allow initializer forms everywhere, but only allow init properties to be initialized when the method is marked with Factory. However, almost all uses of this syntax would be a mutation of the receiver, and it may not be clear that the initializer syntax produces a mutation.

As to whether null should be a valid return: most people think no. Since almost all initializer forms dereference the receiver, this is essentially creating a very obscure way of producing exceptions. In addition, all struct values should be permitted, as they are all safe. default should be legal if the target type is known to be a struct. We have not considered what the behavior should be for unconstrained generics.

There also some concerns about the syntactic extensions. First in that this would make identifier { ... } a valid syntactic form in most situations. This may not be syntactically ambiguous today, but we have a lot of different features, like block expressions, which share some syntactic similarity. Even if there is no syntactic ambiguity, some people are still concerned that the feature will be too opaque. One way to remedy this would be to require the new keyword for this form as well. So the new syntax would be:

var s = new Create() { Name = "Andy" };

There could be some naming ambiguity here because Create could be either a factory method or a type name. We would have to preserve the interpretation as a type here for compatibility.

There's a broader question of how or if we'd like a general initializer feature. There's some question of whether the feature is useful enough to deserve the complexity at all, using any additional syntax. Alternatively, we could embrace the syntax requiring the new keyword.

One important piece of history is that initializers are not meant for mutating existing state, only for mutating new objects. This doesn't necessarily conflict with allowing initializers on any object, but the reason here is not that the language is suggesting using object initializers for arbitrary mutation, but that convention alone is good enough to promote use on "new" objects only.

Regardless of the extensions of the feature, we certainly need to implement something for records. The core feature requirement here is for the with expression, which needs to assign to init fields. We can head two directions: special case the Clone method, or build a more general feature. This is a spectrum, where one end may be a new syntactic form specific to just the Clone method, and the other end could be a Factory attribute that could be applied to any method.

Conclusion

Right now we're more concerned with what to do for records. In the meantime, let's not support user-written Clone methods. A Clone method will be generated for a record with an unspeakable type and the SpecialName flag. The with expression will look for exactly that method name. We intend to decide for C# 9 how that method will be written in source. We'll consider broader Factory scenarios later.