オブジェクトの同一性を見分ける EqualityComparer のバックアップソース(No.1)

更新

[[公開メモ]]

* object.Equals と object.GetHashCode [#qe6373a9]

これらをオーバーライドすることで、同一の内容を持つ異なるオブジェクトを
同一視することができる。

 LANG:C#
 using System.Collections.Generic;
 
 class TestClass
 {
     public string text;
     public override bool Equals(object obj)
     {
         return ( obj is TestClass ) && ( (TestClass)obj ).text == text;
     }
     public override int GetHashCode()
     {
         return text.GetHashCode() * 13;
     }
     public TestClass(string text)
     {
         this.text = text;
     }
 }
 
 ...
     var a = new TestClass("a");
     var b = new TestClass("a");
     var list = new List<TestClass>();
 
     Assert.IsFalse( a == b );
     Assert.AreEqual(a, b);
     
     list.Add(a);
     Assert.IsTrue(list.Contains(b)); // 同じ内容の異なるインスタンス

* 今度は見分けられなくなる [#n6b374cc]

で、これはこれで良いのだけれど、今度は異なるインスタンスを見分けたい時に困る。

解決するには EqualityComparer というのを実装すれば良いと書いてあるのだが、

.Equals( a, b ) は、return a == b; とか、return object.ReferenceEquals(a, b); 
などとすれば良いとして、

.GetHashCode( a ) をどうするか。object.GetHashCode を呼び出したくても
オーバーライドしてしまったものは正攻法で呼び出すことができない。

そこで、リフレクションの System.Reflection.MethodInfo.GetBaseDefinition() 
を使って以下のように解決した。

 LANG:C#
 using System.Collections.Generic;
 
 /// <summary>
 /// Equality comparer that uses Object.ReferenceEquals(x, y) to compare class values.
 /// </summary>
 /// <typeparam name="T"></typeparam>
 public class EqualityComparerByRef<T>: EqualityComparer<T>
     where T: class
 {
     /// <summary>
     /// Determines whether two objects of type  T are equal by calling Object.ReferenceEquals(x, y).
     /// </summary>
     /// <param name="x">The first object to compare.</param>
     /// <param name="y">The second object to compare.</param>
     /// <returns>true if the specified objects are equal; otherwise, false.</returns>
     public override bool Equals(T x, T y)
     {
         return Object.ReferenceEquals(x, y);
     }
 
     System.Reflection.MethodInfo getHashCode;
     /// <summary>
     /// Initializes a new instance of the EqualityComparerByRef<T> class.
     /// </summary>
     public EqualityComparerByRef()
     {
         var getHashCode = typeof(T).GetMethod("GetHashCode", Type.EmptyTypes);
         while ( getHashCode.DeclaringType != typeof(object) )
             getHashCode = getHashCode.GetBaseDefinition();
     }
 
     /// <summary>
     /// Serves as a hash function for the specified object for hashing algorithms and 
     /// data structures, such as a hash table.
     /// </summary>
     /// <param name="obj">The object for which to get a hash code.</param>
     /// <returns>A hash code for the specified object.</returns>
     /// <exception cref="System.ArgumentNullException"><paramref name="obj"/> is null.</exception>
     public override int GetHashCode(T obj)
     {
         return (int)getHashCode.Invoke(obj, new object[0]);
     }
 
     /// <summary>
     /// Returns a default equality comparer for the type specified by the generic argument.
     /// </summary>
     /// <value>The default instance of the System.Collections.Generic.EqualityComparer<T>
     ///  class for type T.</value>
     public static EqualityComparerByRef<T> Default { get { return _default; } }
     static EqualityComparerByRef<T> _default = new EqualityComparerByRef<T>();
 }
 
 ...
 
     var a = new TestClass("a");
     var b = new TestClass("a");

     var DefaultComparer = EqualityComparer<TestClass>.Default;
     var ComparerByRef = EqualityComparerByRef<TestClass>.Default;
     
     Assert.IsTrue(DefaultComparer.Equals(a, a));    // same object
     Assert.IsTrue(ComparerByRef.Equals(a, a));      // same object

     Assert.IsTrue(DefaultComparer.Equals(a, b));    // different object with same value
     Assert.IsFalse(ComparerByRef.Equals(a, b));     // different object with same value
 
     var list = new List<TestClass>();

     list.Add(a);
     Assert.IsTrue(list.Contains(b));
     Assert.IsFalse(list.Contains(b, ComparerByRef));

* コメント [#xe6efdeb]

Counter: 12912 (from 2010/06/03), today: 1, yesterday: 0