csharplang/meetings/2019/LDM-2019-10-21.md
2019-10-23 15:43:53 -07:00

5.1 KiB

C# Language Design Meeting for Oct. 21

Agenda

  1. Records
  2. Init-only members
  3. Static lambdas

Discussion

We'd like to make some progress on records, in both design and implementation. One development in our view of records is to consider them not as a feature in and of itself, but a shorthand for a set of features aimed at working with a collection of data. Specifically, we have a proposal to consider a guiding principle: a record should only be capable of generating code that the user could write themselves.

To that end, we can look at the set of features we're considering incorporating into records, and differentiate between them based on the uncertainty of their design. The features which may have generated behavior are:

  1. Automatic structural equality

  2. With-ers (per type?)

  3. Object initializers for readonly (per member)

  4. Primary constructors + deconstruction

Some features, like with-ers and object initializers, have many open issues. Some features, like generation of structural equality, have widely agreed upon semantics, once the set of members that are part of the structure are decided.

It's proposed that we take a subset of the features, namely primary constructors and structural equality, and consider them to have designs ready for implementation. Primary constructors still have open semantic questions, especially around their meaning outside of a records, but all of the proposed records specify some type of primary constructor with very similar semantics. An example of the common semantics would be the following,

data class Person(string Name, DateTime Birthday);

which would generate two record members, Name and Birthday, structural equality on those two members, and a corresponding constructor/deconstructor pair. The data modifier, as well as the primary constructor, are not necessarily final syntax, but the semantic decisions downstream of this stand-in don't heavily depend on the specific form of syntax chosen and could be easily changed.

Conclusion

This looks like a reasonable starting point. Records and the components should definitely be designed together, to ensure they fit together well, but it's worth highlighting the more settled pieces as we go.

Init-only

A brief discussion of the init-only fields feature. One clarification: the init-only properties would be allowed to have setters. Those setters can be executed in the object initializer, or in the constructor of the object. There is a proposal to allow them also to be set in the constructors of base types, which has no opposition at the moment.

There's also a problem with "required" init-only members as currently proposed. The current design specifies that "required" init-only members (members without an initializer) would produce an error on the construction side if the member is not initialized in an object initializer.

For example,

class Person
{
    public initonly string Name { get; }
}

void M()
{
    var person1 = new Person() { Name = "person1" }; // OK
    var person2 = new Person(); // Error, Name was not initialized
}

Unfortunately, the proposed emit strategy for initonly members would only provide an error in source, not in metadata. If a consumer compiled against a previous implementation of a type, and a required initonly member was added, no error would be provided if the consumer were not recompiled. Instead, the member would silently be set to the default value.

A simple alternative is to drop "required" initonly members entirely. Setting an initonly member would be optional, as it is for public readonly members today, and if it is not set it would retain its default value. The recommendation for adding required members would be the same as it is today: use a constructor parameter, which cannot be skipped.

We could also attempt to repair the situation by lowering the required initonly members into constructor parameters, but this seems undesirable for many reasons, including that it's complex to implement, it risks creating collisions with constructor overloads with the same type, it creates a mismatch in the number of parameters between source and IL, etc. It does not seem worth going down this path.

Static lambdas

Static lambdas were elided mostly for time reasons and we're interested in bringing them back. The main hang-up on the syntax is adding modifiers before the lambda syntax, but we already crossed that bridge with async.

The only semantics question is whether or not a static lambda guarantees that the emitted method will be static in metadata. This is true for static local functions, but there are some important scenarios, like extern local functions, which depend on that and could not be implemented for lambdas. In addition, there have been numerous performance improvements in lambdas and local functions that have taken advantage of their unspecified metadata characteristics, and at least one significant performance optimization for lambdas would be lost by requiring them to be static.

Conclusion

The static keyword will be allowed on lambdas and it will have the same capturing rules as static local functions. It will not require that the lambda be emitted as static.