71

我只是想知道为什么该语言的设计者决定在匿名类型上实现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
}

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

4

4 回答 4

77

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

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

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

于 2012-08-25T16:05:31.767 回答
39

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

但我在 Eric Lippert 关于匿名类型统一在一个程序集中的文章中发现了这一点,第二部分

匿名类型为您提供了一个方便的位置来存储一小部分不可变的名称/值对,但它提供的还不止这些。它还为您提供了 Equals、GetHashCode 以及与本讨论最密切相关的 ToString 的实现。(*)

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

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

于 2012-08-25T16:12:12.193 回答
22

C# 语言规范的官方答案(可在此处获得):

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

(我的重点)

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

值得注意的是,在VB.Net中,实现是不同的:

没有键属性的匿名类型的实例仅与自身相同。

创建匿名类型对象时,必须明确指出关键属性。默认值为:无键,这对 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}
于 2012-08-25T18:13:45.470 回答
9

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

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

第三点值得研究。

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

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

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

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

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

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

于 2012-08-25T18:55:14.047 回答