
# C# Language Design for Feb. 19, 2020

## Agenda

State-based Value Equality

## Discussion

Proposal: https://github.com/dotnet/csharplang/issues/3213

* We haven't decided (yet) to add support for value equality on all
  classes (separate from records)

* The behavior is actually that all fields _declared_ in the class are
  members in the value equality, not all fields in the class (since inherited fields are not
  included)

    * Inheritance would be implemented using the previously described
      proposals using the `EqualityContract` property

* Records wouldn't behave differently, except that they have `value` by
  default

* The main difference with how records work in other places is that the semantics
of a record is otherwise decided by the members of the primary constructor, while in this
proposal the members of the record primary constructor have no special contribution to the value
equality semantics

* There's an evolution risk where we want to provide more complex things, like deep
equality, but these features don't support enough complexity to add it. Instead, we end up just
adding more keywords or more attributes. Consider array fields. The default equality is reference
equality, but sequence equality isn't particularly rare. How would users customize that?
A new keyword? Attribute? Writing Equals manually?

  * Turns out we're finding a lot of customization pivots. String comparison is another one.
    If we want to support all these scenarios attributes could be better. If we could use
    attributes to supply an EqualityComparer that would be almost completely customizable.

  * If equality is this complicated, should we only support simple generated equality for
  records? Can we leave more complicated scenarios to tooling, like source generators?

Record equality: use the "primary" members or use all fields?

* Using all the fields is consistent with how structs work

* Using the "primary" members mirrors how the generation of `With` or other things
  generated by a record with a primary constructor

* There does seem to be a possibility that after you get to a certain size, positional
  records are less useful. In that case we want a path to the nominal record. If we do want the
  nominal path, it's generally desirable that we want as little "record" syntax as possible.
  If we choose the struct "use all the fields" approach, then we could use exactly the
  same mechanism for both the "nominal" and the "positional" records.

* The nominal record syntax that has been floated is

```C#
record Point { int X; int Y; }
```

which generates

```C#
record Point
{
    public int X { get; init; }
    public int Y { get; init; }
}
```

Aside from the shorthand for properties, this generates Equals, GetHashCode and some form
of "With", which doesn't seem much different from proposals for a separable value equality. Is
there really much point in separating these proposals?

* One completely opposite possibility: bypass the question by prohibiting private members in the
positional record entirely

**Conclusion**

No hard decisions yet. Leaning slightly towards using "all declared fields" as the metric for
value equality. There's some support for the "no private fields approach."
