在编写 C# 代码时,我倾向于编写许多实现了 IEquatable<T>
、IComparable<T>
或两者都实现的值类型对象。
为了这个提案,假设我正在编写一个名为 Int256
的虚构结构体,它具有可比较和可相等的价值语义,例如:
public readonly struct Int256 : IEquatable<Int256>, IComparable<Int256>, IComparable
{
public bool Equals(Int256 other)
{
// TODO : is this equal to other?
}
public int CompareTo(Int256 other)
{
// TODO : how does this compare to other?
}
public int CompareTo(object? obj)
{
if (obj is null) return 1;
if (obj is not Int256 int256) throw new ArgumentException("Obj must be of type Int256.");
return CompareTo(int256);
}
public static bool operator ==(Int256 left, Int256 right)
{
return Equals(left, right);
}
public static bool operator !=(Int256 left, Int256 right)
{
return !Equals(left, right);
}
public static bool operator >(Int256 left, Int256 right)
{
return left.CompareTo(right) is 1;
}
public static bool operator >=(Int256 left, Int256 right)
{
return left.CompareTo(right) is 1 or 0;
}
public static bool operator <(Int256 left, Int256 right)
{
return left.CompareTo(right) is -1;
}
public static bool operator <=(Int256 left, Int256 right)
{
return left.CompareTo(right) is -1 or 0;
}
}
假设我创建了一些其他具有相同语义的虚构结构体,例如UInt256
和Decimal256
。虽然微不足道,但这些运算符对于每个值类型对象都变得繁琐。
最近,我一直在研究C# 11的新语言特性,特别是静态接口方法,我认为这在很大程度上使新的通用数学接口成为可能。考虑到这一点,我可以向我的实现添加一些附加接口,特别是IEqualityOperators<TSelf, TOther, TResult>
和IComparisonOperators<TSelf, TOther, TResult>
,例如:
public readonly struct Int256 : IEquatable<Int256>, IComparable<Int256>, IComparable, IComparisonOperators<Int256, Int256, bool>
{
...
}
为了完整起见,在这里没有必要实现IEqualityOperators<TSelf, TOther, TResult>
,因为它们已经被IComparisonOperators<TSelf, TOther, TResult>
扩展了。
归根结底,这并不能真正解决问题。所有这些接口所做的只是确保操作符被实现。
我的建议是是否可以设计出一些接口,能够自动实现与IEquatable<T>
和IComparable<T>
相关的一些典型样板代码,特别是操作符:==
、!=
、>
、>=
、<
、<=
;例如:
IAutoEquatable<T>
public interface IAutoEquatable<T> : IEquatable<T>, IEqualityOperators<T, T, bool> where T : IAutoEquatable<T>
{
// Auto-implemented boilerplate.
static virtual bool operator ==(T? left, T? right)
{
return Equals(left, right);
}
// Auto-implemented boilerplate.
static virtual bool operator !=(T? left, T? right)
{
return !Equals(left, right);
}
}
IAutoComparable<T>
public interface IAutoComparable<T> : IComparable<T>, IComparable, IComparisonOperators<T, T, bool> where T : IAutoComparable<T>
{
// Auto-implemented boilerplate.
static virtual bool operator ==(T? left, T? right)
{
return Equals(left, right);
}
// Auto-implemented boilerplate.
static virtual bool operator !=(T? left, T? right)
{
return !Equals(left, right);
}
// Auto-implemented boilerplate.
static virtual bool operator >(T left, T right)
{
return left.CompareTo(right) is 1;
}
// Auto-implemented boilerplate.
static virtual bool operator >=(T left, T right)
{
return left.CompareTo(right) is 1 or 0;
}
// Auto-implemented boilerplate.
static virtual bool operator <(T left, T right)
{
return left.CompareTo(right) is -1;
}
// Auto-implemented boilerplate.
static virtual bool operator <=(T left, T right)
{
return left.CompareTo(right) is -1 or 0;
}
}
这里的意图是实现者只需要实现
bool Equals(T other)
和int CompareTo(T other)
,但由于运算符已经在接口上实现,所以它们自动获得了运算符的实现。以我的示例为例,代码可能如下所示:
public readonly struct Int256 : IEquatable<Int256>, IComparable<Int256>, IComparable
{
public bool Equals(Int256 other)
{
// TODO : is this equal to other?
}
public int CompareTo(Int256 other)
{
// TODO : how does this compare to other?
}
public int CompareTo(object? obj)
{
if (obj is null) return 1;
if (obj is not Int256 int256) throw new ArgumentException("Obj must be of type Int256.");
return CompareTo(int256);
}
}
但我仍然可以使用运算符; 例如:
Int256 a = 123;
Int256 b = 456;
a == b; // False
a != b; // True
a > b; // False
a >= b; // False
a < b; // True
a <= b; // True
然而,有一个问题。
虽然这些接口IAutoEquatable<T>
和IAutoComparable<T>
包含了运算符的实现,但我仍然需要在Int256
中实现它们。
问题
- 为什么接口中的虚拟默认实现仍然需要实现?即为什么
Int256
不能使用默认实现? - 未来的C#版本是否可能解决这个问题,以便我们可以使用它来减轻编写样板代码的需求?
在此与C#语言设计团队讨论:https://github.com/dotnet/csharplang/discussions/7032
Foo.op_Equality
(如果找到),否则解析为Object.ReferenceEquals
。这是一种非常反直觉的行为。 - JL0PD