2012-08-25 64 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是不是我剛剛說的? –