csharplang/meetings/2020/LDM-2020-03-23.md
2020-03-25 15:18:45 -07:00

4.8 KiB

C# Language Design Meeting for March 23rd, 2020

Agenda

  1. Triage
  2. Builder-based records

Discussion

Triage

Generic constraint where T : ref struct

Proposal: #1148

This is a very complicated area. It's probably not good enough to add this generic constraint, because things like default interface methods create a boxed this parameter. It's likely that we would need runtime support to make this safe.

This is somwewhat related to the designs in the shapes/roles proposal in that it's about using the "shape" of an interface, possibly with more restrictions than interfaces themselves. Since both proposals may require runtime changes it would be valuable to batch up those changes together.

Conclusion

Push to at least C# 10.0. Should be considered in concert with the shapes/roles proposals.

Improve overload resolution for delegate compatibility

Proposal: #3277

This is a parallel to changes we previously made to betterness where we remove overloads that will later be considered invalid, to be removed from the overload resolution candidate list in the beginning. This functionally will cause more overload resolution calls to succeed, since invalid candidates will be removed and this will prevent overload resolution ambiguitiy.

Conclusion

Tentatively added to C# 9.0. We'd like this proposal for function pointers, so if we were to implement this for function pointers and correct overload resolution for delegates at the same time, that would be desirable.

Allow GetEnumerator from extension methods

Proposal: #600

First thing is to confirm that there's no compat concern. When we tried to extend the behavior for IDisposable we ran into a problem because foreach conditionally disposes things which match the IDisposable pattern. This means that if anything starts satisfying the pattern which didn't before, an existing method may be newly called. If we ever conditionally use the foreach pattern this would probably also be a breaking change.

Conclusion

Willing to accept it any time, as long as we confirm that it's not a breaking change.

Builder-based records

When discussing records we've had various designs that focus on "nominal" scenarios, where the members of the record are set by name, instead of lowering into method parameters. One proposed implementation strategy is a new series of rules around initialization that we've sometimes called "initonly." We've also looked at using struct "builders" in the past for a similar purpose, and would like to revisit some of these discussions.

We have a proposal from a community member, @HaloFour, that lays out another example implementation strategy that we're using for discussion.

https://gist.github.com/HaloFour/bccd57c5e4f3261862e04404ce45909e

There are certainly some advantages to this structure:

  • Uses existing valid C# to implement the pattern, making it compatible with existing compilers. If we avoid usage of features like ref struct and in, it could be compatible for even older consumers, since this would be binary compatible with C# 2.0 metadata.

  • Allows the type being built to always see the whole set of property values being initialized, meaning that the author of the type can validate the new state of the object.

  • No new runtime support for any features (e.g., does not require covariant returns)

There are also some disadvantages.

Performance could be a problem. For classes, the biggest concern is stack size. Currently, initializing a class with an object initializer only requires a single pointer on the class and then each member is initialized separately, the initializer values don't all need to be on the stack simultaneously. If we use a struct builder, the entire builder needs to be on the stack before initialization.

For structs, there is the extra stack space cost, but having a builder also effectively doubles the metadata size of the every struct. It's also potentially harder for the CLR to optimize the initialization and remove dead stores through the double-struct passing. If we go forward with this approach we should consult the JIT for their perspective.

We're also not sure that this approach fully eliminates all brittleness in adding/changing fields and properties across inheritance, especially if the inheritance is split across assemblies and only one author is recompiled, or they are recompiled in a different order. If we can find a way to close those holes, or limit the feature to prevent these situations, that could be an important mitigating factor.

Conclusion

There's a difficult balance here. Some members are focused on about performance, some prioritize ecosystem compat, and others prioritize "cleanliness" of design, in different directions. Almost everyone has a different priority and prefers different approaches for different reasons. We need to discuss things more and reduce some of the unknowns.