首页 > 解决方案 > 为只读结构实现相等的最佳实践是什么?

问题描述

我去年刚开始用 C# 编程,我还在学习这门语言。我有一个关于readonly struct类型和相等比较方法的问题。

在 C# 中创建结构时,我知道实现IEquatable通常被认为是最佳实践,因为默认的基于反射的比较非常慢。我还了解到,在 C# 7.2 及更高版本中,我们可以定义readonly结构,对于这些类型,我们还可以使用in参数来避免不必要的复制。

由于结构通常被定义为不可变的只读类型,我想为只读结构定义 Equals 方法并不罕见。

然而,鉴于上述事实,我想知道是否有一种有效的方法来为它们实现相等比较方法。我的观点是,这些相等方法和运算符实际上都不需要修改参数,所以我想以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以上当然有效,但我认为这不是完美的解决方案,因为如果通过接口调用它仍然需要复制。请注意,我不能只IEquatable使用 param 隐式实现,in因为采用修饰符的 Equal 方法被认为具有不同的签名并被视为重载。

是否有已知的最佳实践来正确实施? 我真正想知道的是,是否有已知的最佳实践和模式可以有效地为此类只读结构实现相等性。特别是我对正确利用in参数修饰符来实现平等比较方法的方法感兴趣。

目前在网上还没有找到满意的答案,也查了一些核心库的源码。例如,System.DateTime现在定义为只读结构,它相当大,但这里没有使用参数。(现有类型很可能需要保持兼容性,但我知道它们经常需要妥协。)

请注意,上面定义的 Point 结构很小,仅由两个 32 位插槽组成,因此在这里复制实际上可能不是一个大问题,但这只是一个简单的说明性示例。

.NET6(C# 10)的更新

现在 C#10 已经正式发布,原来的问题几乎已经过时了。我们现在可以创建这样一个只读结构体readonly record struct。当然根据您的模型,它也可以定义为普通的引用类型记录(类)。

https://devblogs.microsoft.com/dotnet/welcome-to-csharp-10/

关键是为记录类型自动生成优化的相等比较方法和运算符。

    public readonly record struct Person
    {
        public string FirstName { get; init; }
        public string LastName { get; init; }
    }

标签: c#structc#-7.2

解决方案


但是,上面的内容很好:请注意,in只读值类型的优势仅适用于大小不重要的类型;在两个整数的情况下,你可能想多了。

您无法消除在IEquatable<T>场景中使用按值传递的需要,因为这就是 API 的定义方式。

还可能值得注意的是,这种in用法可能会使从 C# 以外的语言使用此 API 变得困难;例如,VB 对此的支持很差。这是否重要取决于您的目标受众。


推荐阅读