为只读结构实现相等的最佳实践是啥? [关闭]

Posted

技术标签:

【中文标题】为只读结构实现相等的最佳实践是啥? [关闭]【英文标题】:What is the best practice to implement equality for readonly structs? [closed]为只读结构实现相等的最佳实践是什么? [关闭] 【发布时间】:2020-06-26 17:55:08 【问题描述】:

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

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

由于结构通常被定义为不可变的只读类型,我想为只读结构定义 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 接口调用它,仍然需要复制。请注意,我不能只使用in 参数隐式实现IEquatable,因为接受修饰符的Equal 方法被认为具有不同的签名并被视为重载。

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

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

请注意,上面定义的 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; 
    

【问题讨论】:

IMO,您的代码看起来还不错。您可以查看有关 readonly 结构的博客文章,例如 this,它们显示了类似的方法 我不确定这是否可行(目前无法尝试),但从 C# 8.0 开始,您可以在接口中使用默认实现。这让人想起了拥有自定义 IStructEquatable 泛型接口并拥有 Equals 方法的默认实现的理论可能性,比较两个结构实例的字节表示(当然使用编组)。这将有助于消除重复 Equals、GetHashCode 方法的需要。只是在这里大声思考.. :) 顺便说一下,我知道你是针对 7.2 编译的。正如我所说,只是大声思考。 我个人认为 C# 应该在未来的版本中提供更简单的方法来定义不可变结构。就像匿名类一样,编译器应该默认自动生成合理的相等实现。 这个问题更适合代码审查或软件工程 【参考方案1】:

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

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

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

【讨论】:

感谢您的回答。我同意你的观点,它只对大型结构很重要,简单的 Point 示例可能不是 in 参数的最佳用例。在未来,C# 可能会提供更简单有效的方式来实现不可变结构。 @Ryo "records" in c# vNextProbably 有趣。我知道类似的记录类型在最新的 Java 中,所以在 C# 中也是可取的。 C#已经有类似的数据类型,如匿名类、元组,但前者没有名称和分配堆,后者是可变的。不幸的是,它们都不适合领域模型的值对象。

以上是关于为只读结构实现相等的最佳实践是啥? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

toString 实现的最佳标准样式是啥? [关闭]

规范化 CSS 的最佳实践是啥? [关闭]

除了 syslog 之外,Linux 服务/守护程序文件记录最佳实践是啥? [关闭]

Android:在 Activity 中声明 View 组件的最佳实践是啥? [关闭]

在 ASP.NET 上启动维护过程的最佳实践是啥 [关闭]

在java中存储状态变量的最佳实践是啥? [关闭]