2013-05-13 3 views
9

考えると二つの同じ匿名型のオブジェクト:さらにGetHashCodeを使用して同一の匿名タイプを比較することは安全ですか?

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

そして、彼らは同じ型に解決していないことを前提とし(例えば、それらが異なるアセンブリで定義されるかもしれません)

anonType1.Equals(anonType2); //false 

は、時のことを想定しコンパイル時には、APIが公開されているだけなので、私は1つの構造体(たとえばanonType1)を取得できません。object

したがって、私は以下の手法を考えました:

  1. msgプロパティを取得するためにリフレクションを使用して比較してください。anonType1
  2. dynamic型にanonType1キャストし、各オブジェクトに.GetHashCode()の結果を比較する比較
  3. の動的部材に.msgを参照。

私の質問です:オプション3を使用するのは安全ですか?私。 .GetHashcode()の実装は、現在および将来のすべてのバージョンの.NETフレームワークにおいて、インデント構造ではあるが異なる匿名型に対して常に同じ値を返すと仮定することは賢明でしょうか?

+0

注:私は 'Expression'ベースmemberwiseの比較子を追加 - 便利 –

+0

ブリリアント、おかげかもしれません。 –

答えて

5

興味深い質問です。仕様では、EqualsGetHashcode(仕様の誤字に注意してください)メソッドは同じ型のインスタンスに対して動作しますが、実装は定義されていません。現行のMS C#コンパイラは、シード番号-1134271262と乗数-1521134295のようなマジックナンバーを使用してこれを実装しています。しかし、は仕様の一部ではありません。理論的には、それはC#コンパイラのバージョン間で根本的に変わる可能性があり、それはまだ必要なものを満たすだろう。したがって、2つのアセンブリが同じコンパイラでコンパイルされていない場合、保証はありません。実際、コンパイラがコンパイルするたびに新しいシード値を考えることは "有効"です(しかし、そうではありません)。

個人的には、これを行うにはILまたはExpression技術を使用します。同様の形のオブジェクトを名前でメンバーごとに比較することは、Expressionとかなり簡単です。詳細については

、私もそれが異なりGetHashCode、およびを実装(モノラルコンパイラ)どのようにmcsを見てきました。シード、乗数の代わりに、シード、xor、乗数、シフト、および加算の組み合わせを使用します。したがって、Microsoftと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

+0

@supercat一方、2つのオブジェクトがそれらの間の意味論的な擬似等価でない場合、2つのオブジェクトを比較することはまずありません –

+0

オーバーライドされていないすべての非ヌルの 'X'型と' Y'型(オブジェクト)X.Equals(Y) 'と'((オブジェクト)Y).Equals(X) 'は常に例外なく同じ値を返さなければなりません。型のレポートを持つことは、そのインスタンスを何も知らない無関係な型のものと同じものとして扱い、両方の型のオブジェクトがそこに格納されていると 'Dictionary 'などのコレクションを誤動作させがちです。 – supercat

関連する問題