116 lines
No EOL
4.5 KiB
Markdown
116 lines
No EOL
4.5 KiB
Markdown
|
|
# 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." |