csharplang/meetings/2021/LDM-2021-01-27.md
2021-01-29 18:22:59 -08:00

6.4 KiB

C# Language Design Meeting for Jan. 27th, 2021

Agenda

  1. Init-only access on conversion on this
  2. Record structs
    1. Copy constructors and Clone methods
    2. PrintMembers
    3. Implemented equality algorithms
    4. Field initializers
    5. GetHashcode determinism

Quote of the Day

  • "You don't see the gazillion tabs I have in my other window... It's actually mostly stackoverflow posts on how to use System.IO.Pipelines."

Discussion

Init-only access on conversion on this

https://github.com/dotnet/roslyn/issues/50053

We have 3 options on this issue, centered around how whether we want to make a change and, if so, how far do we want to take it.

  1. Change nothing. The scenario remains an error.
  2. Allow unconditional casts.
  3. Allow as casts as well.

We feel that this case is pretty pathological, and we have trouble coming up with real-world examples of APIs that would need to both hide a public member from a base type and initialize it in the constructor to some value. It would also be odd to allow it in the constructor while not having a form of initializing the property from an object initializer, which is the new thing that init enables over set methods. If we continue seeing a need to name hidden members, perhaps we can come up with a feature that generally allows that, as opposed to solving one particular case in constructors.

Conclusion

We'll go with 1. The proposal is rejected.

Record structs

https://github.com/dotnet/csharplang/issues/4334

Copy constructors and Clone methods

We're revisiting the decision made the last time we talked about record structs. In that meeting, we decided to disallow record structs from defining customized with semantics, due to concerns over how such structs would behave in generic contexts when constrained to where T : struct. If we do disallow this customization, do we need to disallow methods named Clone as well? And should we also disallow copy constructors? In looking at the questions here, we spitballed some potential ways that we could allow customized copies and still allow record structs to be used in generics constrained to where T : struct, potentially by introducing a new interface in the BCL. A with on a struct type param would check for an implementation of that interface and call that, rather than blindly emitting a dup instruction as our original intention was. We think it's an interesting idea and want to pull it into a complete proposal, so we're holding off on making any decisions about allowed and disallowed members in record structs related to copying for now.

Conclusion

On hold.

PrintMembers

A question was raised during initial review of the specification for record structs on whether we need to keep PrintMembers. Struct types don't have inheritance, so we could theoretically simplify that to just ToString() for this case. However, we think that there is value in minimizing the differences between record structs and record classes, so conversion between them is as painless as possible. Since users can provide their own PrintMembers method with their own semantics, removing it potentially introduces friction in making such a change.

Conclusion

Keep PrintMembers. The behavior will be the same as in record classes.

Implemented equality algorithms

During review, a question was raised about our original decision here with respect to generating the actual equality implementation for record structs. Originally, we had decided to not generate a new Equals(object) method, and have making a struct a record be solely about adding new surface area for the same functionality. Instead, we'd work with the runtime to make the equality generation better for all struct types. While we still want to pursue this angle as well, after discussion we decided that this would be another friction point between record classes and record structs, and could potentially have negative consequences if we don't have time to ship better runtime equality for structs in .NET 6, as many scenarios would then just need to turn around and implement equality themselves. In the future, if we do get the better runtime-generated equality, that could be added as a feature flag to System.Runtime.CompilerServices.RuntimeFeature, and we can inform the generation of equality based on the presence of the flag.

Conclusion

We will generate equality methods in the same manner as proposed in the record struct specification proposal.

Field initializers

The question here is whether we can allow field initializers in structs that have a primary constructor with more than zero parameters. The immediate followup to that question, of course, is can we just finally allow parameterless constructors for structs in general, and then field initializers just work for all of them? We're still interested in doing this: the Activator.CreateInstance bug was in a version of the framework that is long out of support at this point, and we have universal agreement behind the idea. The last time we talked about the feature we took a look at non-defaultable value types in general, and while there are interesting ideas in there, we don't think we need to block parameterless constructors on it.

Conclusion

Let's dig up the proposal from when we did parameterless struct constructors last and get it done, then this question becomes moot.

GetHashcode Determinism in Combine

The record class specification, and the record struct specification, states:

The synthesized override of GetHashCode() returns an int result of a deterministic function combining the values of System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN) for each instance field fieldN with TN being the type of fieldN.

We're not precise on the semantics of "a deterministic function combining the values" here, and the question is whether we should be more precise about the semantics of that. After discussion, we believe we're fine with the wording. It does not promise determinism across boundaries such as different executions of the same program or running the same program on different versions of the runtime, which are not guarantees we want to make.

Conclusion

Fine as is.