【问题标题】:Why does the Equals implementation for anonymous types compare fields?为什么匿名类型的 Equals 实现会比较字段?
【发布时间】:2012-08-25 16:02:55
【问题描述】:

我只是想知道为什么该语言的设计者决定在匿名类型上实现 Equals,类似于在值类型上实现 Equals。这不是误导吗?

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public static void ProofThatAnonymousTypesEqualsComparesBackingFields()
{
    var personOne = new { Name = "Paweł", Age = 18 };
    var personTwo = new { Name = "Paweł", Age = 18 };

    Console.WriteLine(personOne == personTwo); // false
    Console.WriteLine(personOne.Equals(personTwo)); // true
    Console.WriteLine(Object.ReferenceEquals(personOne, personTwo)); // false

    var personaOne = new Person { Name = "Paweł", Age = 11 };
    var personaTwo = new Person { Name = "Paweł", Age = 11 };
    Console.WriteLine(personaOne == personaTwo); // false
    Console.WriteLine(personaOne.Equals(personaTwo)); // false
    Console.WriteLine(Object.ReferenceEquals(personaOne, personaTwo)); // false
}

乍一看,所有打印的布尔值都应该是假的。但是当使用Person 类型和匿名类型时,带有Equals 调用的行返回不同的值。

【问题讨论】:

  • 这是 SO 上为数不多的有 3 个或更多答案的帖子之一。截至 2017 年 5 月 23 日,回答的人已超过 95k

标签: c# c#-3.0


【解决方案1】:

匿名类型实例是不可变的数据值,没有行为或身份。参考比较它们没有多大意义。在这种情况下,我认为为它们生成结构上的平等比较是完全合理的。

如果您想将比较行为切换为自定义(引用比较或不区分大小写),您可以使用 Resharper 将匿名类型转换为命名类。 Resharper 还可以生成相等成员。

这样做还有一个非常实际的原因:匿名类型可以方便地用作 LINQ 连接和分组中的哈希键。因此,它们需要语义正确的 EqualsGetHashCode 实现。

【讨论】:

  • 我仍然很困惑,为什么匿名类型 == 不比较 .Equals 之类的值?
  • 因为 == 不会在 C# 中调用 Equals(从不!)。它调用operator == 而匿名类型没有那个。所以 C# 使用引用相等。是好还是坏?谁知道,因为这是一个权衡。毕竟,我从来没有觉得需要比较两个匿名类型实例。由于某种原因永远不会发生。
  • 我同意从方便的角度来看它很聪明(GroupBy() 等),但从技术角度来看有点欺骗性。 MSDN 声明“匿名类型是直接从对象派生的类类型......”。但是对象没有实现 IStructuralEquatable,因此该接口以某种方式被注入幕后,使事情变得有点混乱(虽然有用)...
  • 让 operator == 返回与 Equals 不同的东西只是自找麻烦。
  • 很抱歉把这个话题从坟墓里带回来,但在 C# 中,至少相等运算符(==!=)应该只对引用类型进行引用相等,除了类型具有值语义(例如string)。在 OP 的示例中,他们有一个 Person 类,通常人不会被视为值类型。
【解决方案2】:

对于为什么你应该询问语言设计者...

但我在 Eric Lippert 关于Anonymous Types Unify Within An Assembly, Part Two 的文章中发现了这一点

匿名类型为您提供了一个方便的地方来存储小 一组不可变的名称/值对,但它给你的远不止这些。它 还为您提供了 Equals、GetHashCode 和大多数的实现 与此讨论密切相关,ToString。 (*)

为什么部分出现在注释中:

(*) 我们为您提供 Equals 和 GetHashCode,以便您可以使用实例 LINQ 查询中的匿名类型作为要执行的键 加入。 LINQ to Objects 使用哈希表实现连接 性能原因,因此我们需要正确的实现 等于和 GetHashCode。

【讨论】:

    【解决方案3】:

    来自 C# 语言规范的官方答案(可获取here):

    匿名类型上的 Equals 和 GetHashcode 方法覆盖从 object 继承的方法,并根据属性的 Equals 和 GetHashcode 进行定义,因此相同匿名类型的两个实例当且仅当它们的所有属性都是相等的。

    (我的重点)

    其他答案解释了为什么这样做。

    值得注意的是,在 VB.Netimplementation is different:

    没有关键属性的匿名类型的实例只等于它自己。

    创建匿名类型对象时必须明确指出关键属性。默认为:无键,这对 C# 用户来说可能非常混乱!

    这些对象在 VB 中不相等,但在 C# 等效代码中:

    Dim prod1 = New With {.Name = "paperclips", .Price = 1.29}
    Dim prod2 = New With {.Name = "paperclips", .Price = 1.29}
    

    这些对象评估为“相等”:

    Dim prod3 = New With {Key .Name = "paperclips", .Price = 1.29}
    Dim prod4 = New With {Key .Name = "paperclips", .Price = 2.00}
    

    【讨论】:

      【解决方案4】:

      因为它给了我们一些有用的东西。考虑以下几点:

      var countSameName = from p in PersonInfoStore
        group p.Id by new {p.FirstName, p.SecondName} into grp
        select new{grp.Key.FirstName, grp.Key.SecondName, grp.Count()};
      

      之所以有效,是因为匿名类型的 Equals()GetHashCode() 的实现是在逐字段相等的基础上工作的。

      1. 这意味着当在不是 linq-to-objects 的 PersonInfoStore 上运行时,上面的查询将更接近于相同的查询。 (仍然不一样,它将匹配 XML 源将执行的操作,但与大多数数据库的排序规则将不匹配)。
      2. 这意味着我们不必为每次调用 GroupBy 定义一个 IEqualityComparer,这将使匿名对象的 group by 非常困难 - 为匿名对象定义一个 IEqualityComparer 是可能的,但并不容易 - 而且远非如此最自然的意义。
      3. 最重要的是,它不会在大多数情况下造成问题。

      第三点值得研究。

      当我们定义一个值类型时,我们自然想要一个基于值的相等概念。虽然我们可能对基于值的相等性与默认值有不同的想法,例如不区分大小写地匹配给定字段,但默认值自然是明智的(如果性能不佳并且在一种情况下存在错误*)。 (此外,在这种情况下,引用相等是没有意义的)。

      当我们定义一个引用类型时,我们可能需要也可能不需要基于值的相等概念。默认为我们提供了引用相等性,但我们可以轻松更改它。如果我们确实要更改它,我们可以只为 EqualsGetHashCode 或为它们和 == 更改它。

      当我们定义一个匿名类型时,哦等等,我们没有定义它,这就是匿名的意思!我们关心引用相等性的大多数场景不再存在。如果我们要持有一个对象足够长的时间,以便稍后怀疑它是否与另一个相同,那么我们可能不会处理匿名对象。我们关心基于价值的平等的案例很多。经常与 Linq 一起使用(GroupBy,正如我们在上面看到的那样,还有DistinctUnionGroupJoinIntersectSequenceEqualToDictionaryToLookup)并且经常与其他用途一起使用(它是不像我们没有在 2.0 中使用 Linq 为我们做的事情,在某种程度上,在此之前,任何使用 2.0 编码的人都会自己编写 Enumerable 中的一半方法。

      总而言之,我们从匿名类的平等工作方式中获益良多。

      如果有人真的想要引用相等,== 使用引用相等意味着他们仍然拥有它,所以我们不会丢失任何东西。这是要走的路。

      *Equals()GetHashCode() 的默认实现有一个优化,可以在安全的情况下使用二进制匹配。不幸的是,有一个错误使得它有时会错误地将某些情况识别为这种更快的方法是安全的,而实际上它们不是(或者至少它曾经是,也许它已修复)。一个常见的情况是,如果你有一个 decimal 字段,在一个结构中,那么它会认为一些具有等效字段的实例是不相等的。

      【讨论】:

      • 如果没有这种相等实现,就无法对匿名类型进行分组。
      • @PeterRitchie 不是我刚才说的吗?
      猜你喜欢
      • 2016-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-12
      • 1970-01-01
      • 2010-09-25
      相关资源
      最近更新 更多