104 lines
4.8 KiB
Markdown
104 lines
4.8 KiB
Markdown
|
|
||
|
# 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.
|