オブジェクトの同一性を見分ける EqualityComparer の変更点
更新- 追加された行はこの色です。
- 削除された行はこの色です。
- プログラミング/C#/オブジェクトの同一性を見分ける EqualityComparer へ行く。
- プログラミング/C#/オブジェクトの同一性を見分ける EqualityComparer の差分を削除
[[公開メモ]] #contents * 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(); } public int ObjectGetHashCode() // for debug { return base.GetHashCode(); } public TestClass(string text) { this.text = text; } } ... // 同じ値を持つ2つのオブジェクトを作成 var a = new TestClass("a"); var b = new TestClass("a"); // == はオブジェクトの同一性をチェックするので false となる Assert.IsFalse( a == b ); // AreEqual はオブジェクトの同値性をチェックするので true となる Assert.AreEqual(a, b); // List<>.Contains は同値なインスタンスを探索する var list = new List<TestClass>(); list.Add(a); Assert.IsTrue(list.Contains(b)); // Dictionary<,> も同値性を正しく解釈する var dictionary = new Dictionary<TestClass, bool>(); dictionary[a] = true; Assert.IsTrue(dictionary.ContainsKey(b)); Assert.IsTrue(dictionary[b]); * 今度は見分けられなくなる [#n6b374cc] で、これはこれで非常に便利なのだけれど、今度は異なるインスタンスを見分けたい時に困る。 現実の問題として、上記のように独自の同値性を実装した TestClass のようなクラスのインスタンスを__同一性__で判断して Dictionary のようなクラスのインスタンスを、通常通り「同一性」で判断して Dictionary のキーにしたかったのだが、その方法が分からず苦労した。 Dictionary<,> では、オブジェクトの同値性をカスタマイズするために EqualityComparer を実装すれば良いと書いてある。 これには、Equals と GetHashCode を定義することになる。 .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)); * コメント [#xe6efdeb] #article_kcaptcha
Counter: 13740 (from 2010/06/03),
today: 1,
yesterday: 3