2013-05-13 64 views
9

考慮到兩個相同的匿名類型的對象:使用GetHashCode比較相同的匿名類型是否安全?

{msg:"hello"} //anonType1 
{msg:"hello"} //anonType2 

,並假設他們沒有解決同一類型(例如,他們可能會在不同的組件中定義)

anonType1.Equals(anonType2); //false 

此外,假設在編譯的時候,我不能得到一個(說anonType1),因爲API只公開object

因此,對它們進行比較,我認爲以下技術的結構:

  1. 使用反射來獲得anonType1上的msg屬性以進行比較。
  2. anonType1dynamic類型和動態成員對參考.msg用於比較
  3. 比較的.GetHashCode()每個對象上的結果。

我的問題是:使用選項3安全嗎?即假設.GetHashcode()實現總是會爲當前和未來所有.NET框架中的縮進結構但不同的匿名類型返回相同的值,這是否合理?

+0

注:我添加了一個'基於Expression'-成員逐一比較器 - 可能是有用的 – 2013-05-13 09:00:51

+0

輝煌,謝謝。 – 2013-05-13 11:15:59

回答

5

有趣的問題。該規範定義了EqualsGetHashcode(注意規範中的錯字!)方法將針對相同類型的實例運行,但實現未定義。碰巧,當前的MS C#編譯器使用幻數,如-1134271262的種子和-1521134295的乘數來實現此功能。但是不是規範的一部分。從理論上講,這可能會在C#編譯器版本之間發生根本性變化,並且仍然能夠滿足需要。所以,如果2個程序集不是由同一個編譯器編譯的,那麼就沒有保證。事實上,編譯器每次編譯時都會想到新的種子值,這是「有效的」(但不太可能)。

就我個人而言,我會看看使用IL或Expression技術來做到這一點。使用Expression相當容易地通過名稱比較成員相似形狀的對象。

有關信息,我也看了如何mcs(Mono的編譯器)實現GetHashCode,並是不同;而不是種子和乘數,它使用種子,異或,乘數,移位和加數的組合。所以微軟和Mono編譯的同類型將有非常不同GetHashCode

static class Program { 
    static void Main() { 
     var obj = new { A = "abc", B = 123 }; 
     System.Console.WriteLine(obj.GetHashCode()); 
    } 
} 
  • 單聲道:-2077468848
  • 微軟:-617335881

基本上,我不認爲你能保證這一點。


如何:

using System; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Reflection; 
class Foo 
{ 
    public string A { get; set; } 
    public int B; // note a field! 
    static void Main() 
    { 
     var obj1 = new { A = "abc", B = 123 }; 
     var obj2 = new Foo { A = "abc", B = 123 }; 
     Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1, obj2)); // True 

     obj1 = new { A = "abc", B = 123 }; 
     obj2 = new Foo { A = "abc", B = 456 }; 
     Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1, obj2)); // False 

     obj1 = new { A = "def", B = 123 }; 
     obj2 = new Foo { A = "abc", B = 456 }; 
     Console.WriteLine(MemberwiseComparer.AreEquivalent(obj1, obj2)); // False 
    } 

} 

public static class MemberwiseComparer 
{ 
    public static bool AreEquivalent(object x, object y) 
    { 
     // deal with nulls... 
     if (x == null) return y == null; 
     if (y == null) return false; 
     return AreEquivalentImpl((dynamic)x, (dynamic)y); 
    } 
    private static bool AreEquivalentImpl<TX, TY>(TX x, TY y) 
    { 
     return AreEquivalentCache<TX, TY>.Eval(x, y); 
    } 
    static class AreEquivalentCache<TX, TY> 
    { 
     static AreEquivalentCache() 
     { 
      const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance; 
      var xMembers = typeof(TX).GetProperties(flags).Select(p => p.Name) 
       .Concat(typeof(TX).GetFields(flags).Select(f => f.Name)); 
      var yMembers = typeof(TY).GetProperties(flags).Select(p => p.Name) 
       .Concat(typeof(TY).GetFields(flags).Select(f => f.Name)); 
      var members = xMembers.Intersect(yMembers); 

      Expression body = null; 
      ParameterExpression x = Expression.Parameter(typeof(TX), "x"), 
           y = Expression.Parameter(typeof(TY), "y"); 
      foreach (var member in members) 
      { 
       var thisTest = Expression.Equal(
        Expression.PropertyOrField(x, member), 
        Expression.PropertyOrField(y, member)); 
       body = body == null ? thisTest 
        : Expression.AndAlso(body, thisTest); 
      } 
      if (body == null) body = Expression.Constant(true); 
      func = Expression.Lambda<Func<TX, TY, bool>>(body, x, y).Compile(); 
     } 
     private static readonly Func<TX, TY, bool> func; 
     public static bool Eval(TX x, TY y) 
     { 
      return func(x, y); 
     } 
    } 
} 
+0

如果有一個規範精確地定義了編譯器必須創建的匿名類,那麼互操作性將變得微不足道。沒有關於編譯器生成類應該是什麼樣子的規範,我認爲即使他們想成爲這樣的類也不可能互操作。事實上,某些任意類具有名稱和值與匿名類匹配的屬性並不意味着前者的實例應該與後者的實例進行比較。 – supercat 2013-06-03 19:43:34

+0

@supercat另一方面,如果兩個對象之間不是* *語義*僞對等關係,那麼您不可能比較兩個對象。 – 2013-06-03 19:54:52

+0

對於所有非空「X」和「Y」類型的非破壞覆蓋Equals(object)','((object)X).Equals(Y)'和'((object)Y).Equals(X)'應該總是返回相同的值,沒有例外。如果類型報告其實例等於某種對此無關的不相關類型的事件,那麼如果這兩種類型的對象都存儲在其中,則容易導致諸如「Dictionary 」的集合發生故障。 – supercat 2013-06-03 20:13:27

相關問題