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

更新


公開メモ

object.Equals と object.GetHashCode

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

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)); // 同じ内容の異なるインスタンス

今度は見分けられなくなる

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

解決するには 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));

コメント


Counter: 12975 (from 2010/06/03), today: 8, yesterday: 0