
# C# Language Design for Feb 12, 2020

## Agenda

Records

## Discussion

### Value equality

Proposal: use the `key` keyword previously mentioned, but also
require it on the type declaration as well, e.g. 

```C#
key class HasValueEquality
{
    public key int X { get; }
}
```

There are a number of things we could pivot on

```C#
key class HasValueEquality1 { public key int X { get; } }
class HasValueEquality2 { public key int X { get; } }
key class HasValueEquality3 { public key X { get; } }
class HasValueEquality4 : IEquatable<HasValueEquality4> { public int X { get; } }
```

----

```C#
record Point1(int X); // Implies value equality over X
record Point2a(int X); // Implies inherited equality
key record Point2b1(int X); // Implies value equality over X
key record Point2b2a(int X); // Implies "empty" value equality
key record Point2b2b(key int X); // Implies value equality over X


key class Point3a(int X); // implies record + value equality over X
data class Point3b(int X); // implies record with inherited equality
```

#### Equality default

We originally considered adding value equality on records both because it's difficult to
implement yourself and it fits the semantics we built for records in general. We want to validate
that these things are still true, and new considerations, namely whether it is the appropriate
default for records and whether it should be available to other types, like regular classes.

We left off in the previous discussion asking whether value equality is not just
an inconvenient default, but actively harmful for key scenarios for records. Some examples
we came up with are either classes with large numbers of members, where value equality may
be unnecessary and slow, and circular graphs, where using value equality could cause
infinite recursion.

These do seem bad, but it's not obvious that these scenarios either fit perfectly with the
canonical record, or if the consequences are necessarily worse than default reference equality.
Certainly producing infinite recursion in object graphs is bad, but silently incorrect behavior
due to inaccurate reference equality is also harmful, in the same sense. It's also easier
to switch from value equality to reference equality than it is to switch from reference equality
to value equality, due to the complex requirements in a value equality contract.

**Conclusion**

Value equality seems a reasonable default, as long as they are immutable by default, and that
there is a reasonable way to opt-in to a different equality.

#### Separable value equality

Given that we like value equality as a default, we have to decide if we want a separable equality
feature as well. This is important for the scenario:

```C#
record Point1(int X)
{
    public int X { get; }
}
```

if there's a separate `key` feature, we need to decide if the substituted property should
require, allow, or disallow the `key` modifier, e.g.

```C#
record Point1(int X)
{
    public key int X { get; }
}
```

We also need to decide what such a "separable" equality feature would look like, and if it has a
difference between records and other classes. We could add a `key` feature for non-records, and
disallow `key` entirely in records. The members of a record equality would then not be
customizable.

The individual `key` modifiers on non-records seem deceptively complicated.

A common case is "opt-in everything". `key` modifiers wouldn't improve much on this, as they
would be necessary on every element. On the other hand, there are often computed properties that
may be seen as part of "everything", but not part of the equality inputs. The plus of record
primary constructors is that they identify the "core" inputs to the type.

Individual `key` modifiers also do not help with the large custom classes that are written today
where it's easy to forget to add new members to equality. With a `key` modifier you can still
forget to add the modifier to a new member.

These decisions play into records as a whole because they affect the uniformity of record and
non-record behavior. If records are defined by their "parameters", namely in this syntax the
primary constructor parameters and identically named properties, then no other members should
be a part of the equality. However, that would imply members in the body are not automatically
included. For regular classes, it seems backwards. Members are not generally included, they have
to be added specifically. 

On the other hand, if we prioritize uniformity, general members in record bodies would be included
in equality, which would harm a view of records as consisting primarily of the "record inputs."