2012-08-25 33 views
43

就像问题一样,我只是想知道为什么语言的设计者希望在匿名类型上实现Equals,类似于价值类型。这不是误导?为什么匿名类型等于实现比较字段?

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。但是,当使用Person类型并使用匿名类型时,带有Equals调用的行会返回不同的值。

+1

这是SO上为数不多的3个或更多答案的帖子之一,回答者的数量超过** 95k **,截至5月-23-2017。 – dotNET

回答

50

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

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

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

+0

我仍然困惑为什么对于匿名类型==不会比较像.Equals这样的值? –

+10

因为==不会在C#中调用等于(从不!)。它调用'operator ==',匿名类型没有那个。所以C#使用引用平等。是好还是坏?谁知道,因为这是一个权衡。毕竟,我从来没有觉得需要比较两个匿名类型的实例。由于某种原因不会发生。 – usr

+1

我同意它从方便的角度来看很聪明(GroupBy()等),但从技术角度来看有点欺骗。 MSDN指出“匿名类型是直接从对象派生的类类型...”。然而,对象没有实现IStructuralEquatable,所以界面以某种方式被注入幕后,使事情有点混乱(虽然有用)... – Anders

31

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

但是我发现这埃里克利珀的文章关于Anonymous Types Unify Within An Assembly, Part Two

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

凡为什么部分来自于注:

(*),我们给你的Equals和GetHashCode,这样就可以使用实例匿名类型的 在LINQ查询作为键赖以执行 连接。 LINQ to Objects使用散列表实现连接,因为 的性能原因,因此我们需要正确实现 Equals和GetHashCode。

12

从C#语言规范(可获得here)官方答案:

上匿名类型的Equals和GetHashCode方法覆盖从对象继承的方法,和在的Equals和GetHashCode的术语被定义的属性,以便相同的匿名类型的两个实例等于当且仅当它们的所有属性相同时

(我的重点)

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

值得注意的是在VB。Net the 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} 
8

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

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到对象。 (仍然不一样,它将匹配XML源代码将执行的操作,但不符合大多数数据库的归类结果)。
  2. 这意味着我们不必为每个调用GroupBy的每个调用定义IEqualityComparer,这将使得通过匿名对象确实难以分组 - 这对于为匿名对象定义IEqualityComparer是可能的,但并不容易 - 而且远不是最自然的含义。
  3. 最重要的是,它不会导致大多数情况下的问题。

第三点值得研究。

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

当我们定义一个引用类型时,我们可能会或可能不需要一个基于值的相等概念。默认值为我们提供参考平等,但我们可以轻松改变这一点。如果我们改变它,我们可以改变它只为EqualsGetHashCode或为他们和==

当我们定义一个匿名类型时,噢,等等,我们没有定义它,这就是匿名的意思!我们关心引用平等的大多数场景都没有了。如果我们要持续一段时间以便后来想知道它是否与另一个相同,我们可能不会处理一个匿名对象。我们关心基于价值的平等的情况出现了很多。经常使用LINQ(GroupBy就像我们上面看到的,但也DistinctUnionGroupJoinIntersectSequenceEqualToDictionaryToLookup),并经常与其他用途(它不是像我们没有做的事情的LINQ确实为我们提供了可枚举在2.0,并在某种程度上之前,任何编码2的人。0将自己写入Enumerable中的一半方法)。

总而言之,我们从平等与匿名课程的方式中获益颇多。

在别人真的希望引用相等的情况下,==使用引用相等意味着他们仍然有这个,所以我们不会丢失任何东西。这是要走的路。

* Equals()GetHashCode()的默认实现有一个优化,让我们在它可以安全地使用二进制匹配的情况下使用二进制匹配。不幸的是,有一个错误使得它有时会错误地将某些情况误认为是安全的,因为这种情况不是这样(或者至少它曾经用过,也许它是固定的)。一个常见的情况是,如果在结构中有一个decimal字段,那么它会考虑一些等效字段不等的实例。

+1

如果没有这种等式实现,则无法对匿名类型进行分组。 –

+0

@PeterRitchie是不是我刚刚说的? –