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

## Agenda

1. [Init-only access on conversion on `this`](#init-only-access-on-conversion-on-this)
2. [Record structs](#record-structs)
    1. [Copy constructors and Clone methods](#copy-constructors-and-clone)
    2. [`PrintMembers`](#printmembers)
    3. [Implemented equality algorithms](#implemented-equality-algorithms)
    4. [Field initializers](#field-initializers)
    5. [GetHashcode determinism](#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.

