Records: update "Equality members" spec section (#3583)

This commit is contained in:
Charles Stoner 2020-06-19 14:20:47 -07:00 committed by GitHub
parent 0e365431d7
commit d48f35e584
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -39,49 +39,119 @@ The synthesized members are as follows:
### Equality members
Record types produce synthesized implementations of the following methods, where `T` is the
containing type:
The record type includes a synthesized `EqualityContract` readonly virtual property. The property is overridden in each derived record type.
The property can be declared explicitly.
It is an error if the explicit declaration does not match the expected signature or accessibility, or if the explicit declaration is not `virtual` and the record type is not `sealed`.
The synthesized property returns `typeof(R)` where `R` is the record type.
```C#
protected virtual Type EqualityContract { get; };
```
_Can we omit `EqualityContract` if the record type is `sealed` and derives from `System.Object`?_
The record type implements `System.IEquatable<R>` and includes a synthesized strongly-typed overload of `Equals(R? other)` where `R` is the record type.
The method is `public`, and the method is `virtual` unless the record type is `sealed`.
The method can be declared explicitly.
It is an error if the explicit declaration does not match the expected signature or accessibility, or the explicit declaration is not `virtual` and the record type is not `sealed`.
```C#
public virtual bool Equals(R? other);
```
The synthesized `Equals(R?)` returns `true` if and only if each of the following are `true`:
- `other` is not `null`, and
- For each instance field `fieldN` in the record type that is not inherited, the value of
`System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN)` where `TN` is the field type, and
- If there is a base record type, the value of `base.Equals(other)` (a non-virtual call to `public virtual bool Equals(Base? other)`); otherwise
the value of `EqualityContract == other.EqualityContract`.
If the record type is derived from a base record type `Base`, the record type includes a synthesized override of the strongly-typed `Equals(Base other)`.
The synthesized override is `sealed`.
It is an error if the override is declared explicitly.
The synthesized override returns `Equals((object?)other)`.
The record type includes a synthesized override of `object.Equals(object? obj)`.
It is an error if the override is declared explicitly.
The synthesized override returns `Equals(other as R)` where `R` is the record type.
```C#
public override bool Equals(object? obj);
```
The record type includes a synthesized override of `object.GetHashCode()`.
The method can be declared explicitly.
It is an error if the explicit declaration is `sealed` unless the record type is `sealed`.
```C#
public override int GetHashCode();
public override bool Equals(object other);
public virtual bool Equals(T other);
```
`GetHashCode()` and `Equals(object other)` are overrides of the virtual methods in `System.Object`.
Any methods on intermediate base classes that would hide those methods are ignored when overriding.
A warning is reported if one of `Equals(R?)` and `GetHashCode()` is explicitly declared but the other method is not explicit.
Derived record types also override the `Equals(TBase other)` method from each base record type.
The record type synthesizes an implementation of `System.IEquatable<T>` that is implicitly implemented by `Equals(T other)` where `T` is the containing type.
Record types do not synthesize implementations of `System.IEquatable<TBase>` for any base type `TBase`,
even if those interfaces are implemented by the base record types.
The base record class synthesizes an `EqualityContract` property. The property is overridden in
derived record classes. The synthesized implementations return `typeof(T)` where `T` is containing type.
```C#
protected virtual Type EqualityContract { get; }
```
It is an error if the base implementations of any of the overridden members is sealed or non-virtual,
or do not match the expected signature and accessibility.
`Equals(T other)` returns true if and only if each of the following terms are true:
- `other` is not `null`, and
- For each field declared in the record type, the value of
`System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN)` where `TN` is the field type, and
- If there is a base record type, the value of `base.Equals(other)`; otherwise
the value of `EqualityContract.Equals(other.EqualityContract)`.
The overrides of `Equals(T other)` for the base methods, including `object.Equals(object other)`, perform the equivalent of:
```C#
public override bool Equals(object other) => Equals(other as T);
```
`GetHashCode()` returns the `int` result of a deterministic function taking the following values:
- For each field declared in the record type, the value of
The synthesized override of `GetHashCode()` returns an `int` result of a deterministic function combining the following values:
- For each instance field `fieldN` in the record type that is not inherited, the value of
`System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN)` where `TN` is the field type, and
- If there is a base record type, the value of `base.GetHashCode()`; otherwise
the value of `System.Collections.Generic.EqualityComparer<System.Type>.Default.GetHashCode(EqualityContract)`.
For example, consider the following record types:
```C#
record R1(T1 P1);
record R2(T1 P1, T2 P2) : R1(P1);
record R2(T1 P1, T2 P2, T3 P3) : R2(P1, P2);
```
For those record types, the synthesized members would be something like:
```C#
class R1 : IEquatable<R1>
{
public T1 P1 { get; set; }
protected virtual Type EqualityContract => typeof(R1);
public override bool Equals(object? obj) => Equals(obj as R1);
public virtual bool Equals(R1? other)
{
return !(other is null) &&
EqualityContract == other.EqualityContract &&
EqualityComparer<T1>.Default.Equals(P1, other.P1);
}
public override int GetHashCode()
{
return Combine(EqualityComparer<Type>.Default.GetHashCode(EqualityContract),
EqualityComparer<T1>.Default.GetHashCode(P1));
}
}
class R2 : R1, IEquatable<R2>
{
public T2 P2 { get; set; }
protected override Type EqualityContract => typeof(R2);
public override bool Equals(object? obj) => Equals(obj as R2);
public sealed override bool Equals(R1? other) => Equals((object?)other);
public virtual bool Equals(R2? other)
{
return base.Equals((R1?)other) &&
EqualityComparer<T2>.Default.Equals(P2, other.P2);
}
public override int GetHashCode()
{
return Combine(base.GetHashCode(),
EqualityComparer<T2>.Default.GetHashCode(P2));
}
}
class R3 : R2, IEquatable<R3>
{
public T3 P3 { get; set; }
protected override Type EqualityContract => typeof(R3);
public override bool Equals(object? obj) => Equals(obj as R3);
public sealed override bool Equals(R2? other) => Equals((object?)other);
public virtual bool Equals(R3? other)
{
return base.Equals((R2?)other) &&
EqualityComparer<T3>.Default.Equals(P3, other.P3);
}
public override int GetHashCode()
{
return Combine(base.GetHashCode(),
EqualityComparer<T3>.Default.GetHashCode(P3));
}
}
```
### Copy and Clone members
A record type contains two copying members: