csharplang/meetings/2019/LDM-2019-07-22.md
2019-07-29 18:45:13 -07:00

4.8 KiB

C# LDM Notes for July 22, 2019

Agenda

  1. Discuss the new records proposal

Discussion

We started by going over the proposal.

A few comments and clarifications:

Optional and required initialization

The proposal doesn't mention what we consider a problem in existing C# object initializer code: there's no way to specific that members are required or optional. Most notably, a class has no way to force an object initializer to set a member, and no diagnostics will be reported if it is skipped.

The new proposal would produce an error if an initonly member is not set either in the constructor, in an initializer, or in the object initializer. A suggested implementation would be to emit an attribute that indicates whether a member initialization is required or optional, but we do not mandate a specific implementation strategy.

Verification

The philosophy behind the proposed "initonly" verification rules are that we're broadening the CLR's rules slightly. In the CLR, initonly (which is termed readonly in C#) makes the claim that the end of the constructor is the publish boundary for a type, so initonly fields are settable until the constructor finishes, at which point the object is published. We're widening this slightly, by creating rules that ensure that even after the constructor has finished, values can be set before the item is actually published either via a store or a method call. As long as these rules could be efficiently written for a theoretical verifier, we don't strictly need to have an implementation concurrent with the feature.

With-constructor

It also looks like some of the compilicated codegen around the body of the With constructor may be unnecessary. The default implementation may be completely satisfied by object.MemberwiseClone(). However, a With-constructor would likely still be necessary, as object.MemberwiseClone() is protected, and we would like to allow for customizability.

Mutability and code generation

For classes, it would also be good to warn if mutable members are included in a data class, because structural equality is dangerous in the presence of mutability (e.g., if a class is changed after being added to a dictionary, it can "disappear" from the dictionary).

Primary constructors and attributes

The proposed use for primary constructors is short and simple data types. It was noted that many serialization frameworks require a large set of attributes to specify serialization details. For the primary constructor feature, it's an open question of how attributes will work. There is a proposal for putting attributes on the parameters with an additional target, e.g.

data class C([property: MyAttribute]int X);

However, this harms the concision of the feature and may mean that primary constructors are not as useful for serialized types.

Criticisms of new proposal vs original

One major difference between the new proposal and the old proposal is that it implies the existence of a record without a primary constructor. The original motivation for this was to avoid encoding the constructor's positional API (constructors are dependent on argument order) into record construction and use. Thus, to differentiate the two proposals we can refer to them as "nominal" records (records V2) and "positional" records (records V1).

  • Primary constructors as a whole: it's not clear if the pieces are actually that useful outside of the records proposal.

  • Primary constructors + initialization. If people like object initializers, one possible solution is what is proposed here. Another possible solution is to make an object initializer for positional records as a simple syntactic transformation to a record constructor.

  • Source/Binary compatibility. Positional records do not solve the problems of adding or re-ordering members, but it is solvable by manually writing back-compat constructors when adding members.

  • Use cases: it seems like the common case for a few fields (like the UserInfo example) would be more the positional record case. Instead, the common case for nominal records would be big records, like Roslyn's CompilationOptions.

  • Validation: there's no way to validate the consistency of the whole object in this proposal. For instance, many records have requirements that if one field is set, another is set as well, or that they have compatible values. Unfortunately, this is also true of "positional" records, since the With-ers would set values after the construction of the copy completes, meaning that no validation run in the constructor would validate the "With"-ed members. One way to add this functionality would be to enforce a call to a well-known validation method after construction is done.