为只读结构实现相等性的最佳做法是什么?
What is the best practice to implement equality for readonly structs?
我去年才开始用 C# 编程,我仍在学习这门语言。我有一个关于 readonly struct
类型和相等比较方法的问题。
在 C# 中创建结构时,我知道实现 IEquatable as the default reflection-based comparison is very slow. I also learned that in C# 7.2 and later we can define readonly
structs and for these types, we can also use in
parameter 以避免不必要的复制通常被认为是最佳实践。
由于结构通常被定义为不可变的只读类型,我想为只读结构定义 Equals 方法并不罕见。
然而,鉴于上述事实,我想知道是否有一种有效的方法可以为它们实现相等比较方法。我的观点是 none 这些相等方法和运算符实际上需要修改参数,所以我想以某种方式利用 in
参数来节省不必要的复制。
以下是我的尝试:
public readonly struct Point : IEquatable<Point>
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
// Explicitly implementing IEquatable<Point> and delegating to an Equals method taking in param.
bool IEquatable<Point>.Equals(Point other) => Equals(other);
public bool Equals(in Point other) => X == other.X && Y == other.Y;
public override bool Equals(object? obj) => obj is Point other && Equals(other);
public static bool operator ==(in Point left, in Point right) => left.Equals(right);
public static bool operator !=(in Point left, in Point right) => !left.Equals(right);
public override int GetHashCode() => HashCode.Combine(X, Y);
public override string ToString() => $"Point({X}, {Y})";
}
以上确实有效,但我认为这不是完美的解决方案,因为如果通过 IEquatable
接口调用它,仍然需要复制。请注意,我不能仅使用 in
参数隐式实现 IEquatable
,因为采用修饰符的 Equal 方法被认为具有不同的签名并被视为重载。
是否有已知的最佳实践来正确实施?
我真正想知道的是,是否有已知的最佳实践和模式来有效地实现此类只读结构的相等性。我尤其对正确利用 in
用于实现相等比较方法的参数修饰符。
目前在网上还没有找到满意的答案,也查了一些核心库的源码。例如,System.DateTime
现在被定义为只读结构,它相当大,但这里没有使用 in 参数。 (现有类型可能需要保持兼容性,但我知道他们经常需要妥协。)
请注意,上面定义的 Point 结构很小,仅包含两个 32 位槽,因此复制在这里实际上可能不是一个大问题,但这只是一个简单的说明性示例。
.NET6(C# 10)更新
现在 C#10 已经正式发布,原来的问题几乎已经过时了。我们现在可以创建一个 readonly record struct
这样的只读结构。当然根据你的型号,也可以定义为普通的引用类型记录(class).
https://devblogs.microsoft.com/dotnet/welcome-to-csharp-10/
重点是为记录类型自动生成优化的相等比较方法和运算符。
public readonly record struct Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
上面的内容很好,但是:请注意,in
在只读值类型上的优势仅适用于大小非常重要的类型;对于两个整数,您可能想多了。
您无法消除在 IEquatable<T>
场景中使用按值传递的需要,因为那是 API 的定义方式。
可能还值得注意的是,in
用法可能会导致很难从 C# 以外的语言使用此 API;例如,VB 对此的支持很差。这是否重要取决于您的目标受众。
我去年才开始用 C# 编程,我仍在学习这门语言。我有一个关于 readonly struct
类型和相等比较方法的问题。
在 C# 中创建结构时,我知道实现 IEquatable as the default reflection-based comparison is very slow. I also learned that in C# 7.2 and later we can define readonly
structs and for these types, we can also use in
parameter 以避免不必要的复制通常被认为是最佳实践。
由于结构通常被定义为不可变的只读类型,我想为只读结构定义 Equals 方法并不罕见。
然而,鉴于上述事实,我想知道是否有一种有效的方法可以为它们实现相等比较方法。我的观点是 none 这些相等方法和运算符实际上需要修改参数,所以我想以某种方式利用 in
参数来节省不必要的复制。
以下是我的尝试:
public readonly struct Point : IEquatable<Point>
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
// Explicitly implementing IEquatable<Point> and delegating to an Equals method taking in param.
bool IEquatable<Point>.Equals(Point other) => Equals(other);
public bool Equals(in Point other) => X == other.X && Y == other.Y;
public override bool Equals(object? obj) => obj is Point other && Equals(other);
public static bool operator ==(in Point left, in Point right) => left.Equals(right);
public static bool operator !=(in Point left, in Point right) => !left.Equals(right);
public override int GetHashCode() => HashCode.Combine(X, Y);
public override string ToString() => $"Point({X}, {Y})";
}
以上确实有效,但我认为这不是完美的解决方案,因为如果通过 IEquatable
接口调用它,仍然需要复制。请注意,我不能仅使用 in
参数隐式实现 IEquatable
,因为采用修饰符的 Equal 方法被认为具有不同的签名并被视为重载。
是否有已知的最佳实践来正确实施?
我真正想知道的是,是否有已知的最佳实践和模式来有效地实现此类只读结构的相等性。我尤其对正确利用 in
用于实现相等比较方法的参数修饰符。
目前在网上还没有找到满意的答案,也查了一些核心库的源码。例如,System.DateTime
现在被定义为只读结构,它相当大,但这里没有使用 in 参数。 (现有类型可能需要保持兼容性,但我知道他们经常需要妥协。)
请注意,上面定义的 Point 结构很小,仅包含两个 32 位槽,因此复制在这里实际上可能不是一个大问题,但这只是一个简单的说明性示例。
.NET6(C# 10)更新
现在 C#10 已经正式发布,原来的问题几乎已经过时了。我们现在可以创建一个 readonly record struct
这样的只读结构。当然根据你的型号,也可以定义为普通的引用类型记录(class).
https://devblogs.microsoft.com/dotnet/welcome-to-csharp-10/
重点是为记录类型自动生成优化的相等比较方法和运算符。
public readonly record struct Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
上面的内容很好,但是:请注意,in
在只读值类型上的优势仅适用于大小非常重要的类型;对于两个整数,您可能想多了。
您无法消除在 IEquatable<T>
场景中使用按值传递的需要,因为那是 API 的定义方式。
可能还值得注意的是,in
用法可能会导致很难从 C# 以外的语言使用此 API;例如,VB 对此的支持很差。这是否重要取决于您的目标受众。