为什么匿名类型的Equals实现比较字段?

时间:2012-08-25 16:02:55

标签: c# c#-3.0

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

乍一看,所有打印的布尔值都应该为false。但是,如果使用Equals类型,则使用Person调用的行返回不同的值,并使用匿名类型。

4 个答案:

答案 0 :(得分:60)

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

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

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

答案 1 :(得分:36)

为什么你应该问语言设计师......

但我在Eric Lippert关于Anonymous Types Unify Within An Assembly, Part Two

的文章中发现了这一点
  

匿名类型为您提供了存放小号的便利位置   不可变的名称/值对集合,但它为您提供更多。它   还为您提供了Equals,GetHashCode和大多数的实现   与此讨论密切相关,ToString。 (*)

为什么部分出现在说明中:

  

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

答案 2 :(得分:17)

C#语言规范的正式答案(可获得here):

  

匿名类型上的Equals和GetHashcode方法重写从object继承的方法,并根据属性的Equals和GetHashcode定义,因此当且仅当相同的匿名类型的两个实例相等时他们所有的财产都是平等的

(我的重点)

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

值得注意的是,在 VB.Net implementation 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}

答案 3 :(得分: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. 这意味着当针对不是linq-to-objects的PersonInfoStore运行时,上面的内容将更接近相同的查询。 (仍然不一样,它将匹配XML源将执行的操作,但不会与大多数数据库的排序结果相匹配)。
  2. 这意味着我们不必为每次调用IEqualityComparer定义一个GroupBy,这会使匿名对象变得非常困难 - 为匿名对象定义IEqualityComparer是可能的但不容易 - 远非最自然的含义。
  3. 最重要的是,大多数情况下都不会出现问题。
  4. 第三点值得研究。

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

    当我们定义引用类型时,我们可能想要也可能不想要基于值的平等概念。默认值为我们提供了引用相等,但我们可以轻松地改变它。如果我们确实对其进行了更改,我们可以仅针对EqualsGetHashCode或针对他们以及==进行更改。

    当我们定义一个匿名类型时,哦等等,我们没有定义它,这就是匿名的意思!我们关心参考平等的大多数场景都不再存在。如果我们要持有一个物体足够长的时间,以后想知道它是否与另一个物体相同,我们可能不会处理一个匿名对象。我们关心基于价值的平等的案例出现了很多。经常使用Linq(GroupBy,如上所述,还有DistinctUnionGroupJoinIntersectSequenceEqual,{{1和ToDictionary)并且经常有其他用途(这并不像我们没有做Linq为我们用2.0中的枚举做的事情,在某种程度上在那之前,任何用2.0编码的人都会写出一半的方法在ToLookup本身。)

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

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

    * ==Equals()的默认实现有一个优化,让它在安全的情况下使用二进制匹配。不幸的是,有一个错误使得它有时会错误地识别某些情况,因为这些情况对于这种更快的方法是安全的(如果不是这样的话,或者至少习惯了,可能它已经修复)。一个常见的情况是,如果你在一个结构中有一个GetHashCode()字段,那么它会将一些具有等效字段的实例视为不相等。