オブジェクトの同一性を見分ける EqualityComparer の履歴(No.3)
更新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();
}
public int ObjectGetHashCode() // for debug
{
return base.GetHashCode();
}
public TestClass(string text)
{
this.text = text;
}
}
...
var a = new TestClass("a");
var b = new TestClass("a");
Assert.IsFalse( a == b );
Assert.AreEqual(a, b);
var list = new List<TestClass>();
list.Add(a);
Assert.IsTrue(list.Contains(b)); // 同じ内容の異なるインスタンス
var dictionary = new Dictionary<TestClass, bool>();
dictionary[a] = true;
Assert.IsTrue(dictionary.ContainsKey(b));
Assert.IsTrue(dictionary[b]);
今度は見分けられなくなる†
で、これはこれで良いのだけれど、今度は異なるインスタンスを見分けたい時に困る。
解決するには EqualityComparer というのを実装すれば良いと書いてあるのだが、
.Equals( a, b ) は、return a == b; とか、return object.ReferenceEquals(a, b); などとすれば良いとして、
.GetHashCode( a ) をどうするか。object.GetHashCode を呼び出したくても オーバーライドしてしまったものは正攻法で呼び出すことができない。
そこで、オペコードを直に書いて無理矢理 object.GetHashCode を呼び出すことにした。
参照:
http://d.hatena.ne.jp/siokoshou/20070517#p1
http://msdn.microsoft.com/ja-jp/library/exczf7b9(VS.80).aspx
LANG:C#
using System.Collections.Generic;
using System.Reflection.Emit;
public class EqualityComparerByRef<T>: EqualityComparer<T>
where T: class
{
public override bool Equals(T x, T y)
{
return Object.ReferenceEquals(x, y);
}
Func<T, int> getHashCode;
public EqualityComparerByRef()
{
var dm = new DynamicMethod(
"GetHashCodeByRef", // name of the dynamic method
typeof(int), // type of return value
new Type[] {
typeof(T) // type of "this"
},
typeof(EqualityComparerByRef<T>)); // owner
var ilg = dm.GetILGenerator();
ilg.Emit(OpCodes.Ldarg_0); // push "this" on the stack
ilg.Emit(OpCodes.Call,
typeof(object).GetMethod("GetHashCode")); // returned value is on the stack
ilg.Emit(OpCodes.Ret); // return
getHashCode = (Func<T, int>)dm.CreateDelegate(typeof(Func<T, int>));
}
public override int GetHashCode(T obj)
{
return getHashCode(obj);
}
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));
var dictionary = new Dictionary<TestClass, bool>(ComparerByRef);
dictionary[a] = true;
Assert.IsFalse(dictionary.ContainsKey(b));
Assert.AreEqual(b.ObjectHashCode(), ComparerByRef.GetHashCode(b));
コメント†
Counter: 14408 (from 2010/06/03),
today: 1,
yesterday: 0