2013-05-13 37 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

相关问题