値型の等価判定には気をつけるべき点があります。
以下の例で、Assert.IsTrue なら () 内が 真、Assert.IsFalse なら () 内が 偽です。
★印の箇所は注意が必要です。
ボックス化した値の比較
int i = 1;
object o1 = i;
object o2 = i;
// Object.Equals が int の Equals を呼び出すので値で比較されます。
Assert.IsTrue(o1.Equals(o2));
// o1 != o2 かつ o1 != null なら上の o1.Equals(o2) と同じ結果となります。
Assert.IsTrue(Object.Equals(o1, o2));
ここまではいいでしょう。
次は少し注意が必要です。
// ★ボックス化(値型 → object)すると参照比較になるので一致しません。
Assert.IsFalse(o1 == o2);
// ★引数として渡されるときにボックス化されるので一致しません。
Assert.IsFalse(Object.ReferenceEquals(1, 1));
Assert.IsFalse(Object.ReferenceEquals(i, i));
DataRow の各フィールド値(インデクサ/Itemプロパティ)なども Object 型なので、比較の際は注意しましょう。
異なる型の比較
int i = 1;
long l = 1;
// 比較演算子では、int が long に暗黙的に型変換され、long 同士として比較されます。
Assert.IsTrue(i == l);
Assert.IsTrue(l == i);
// 暗黙の型変換ができるなら Equals で比較できます。
Assert.IsTrue(l.Equals(i));
ここまではいいでしょう。
以降は注意が必要です。
// ★暗黙の型変換ができないと Equals は false を返します。
// コンパイルは通り、例外も発生しないので見逃しがちです。
Assert.IsFalse(i.Equals(l));
// ★異なる型へのボックス化解除は失敗します。
object iBoxed = i;
try
{
// int 型をボックス化しているので long 型に戻すことはできません。(InvalidCastException が発生)
bool b = (l == (long)iBoxed);
Assert.Fail();
}
catch (InvalidCastException) {}
おまけです。
// 実行されるのは Object.Equals です。
// 字面だけ見ると true を期待してしまいそう(?)です。
Assert.IsFalse(Type.Equals(1, 2));
値型を自作する場合の注意点
値型(struct/Structure)の Equals メソッドは、既定ではリフレクションを使用して全フィールド(自動実装プロパティのバッキングフィールドを含む)を定義された型の Equals メソッドで比較します。
値型を自分で作成する場合、Equals メソッドをオーバーライドすることで、この比較処理をカスタマイズすることができます。
等価演算子(==)を使用するためには、「==」「!=」のオーバーロードを定義する必要があります。
値型を定義する場合には、Equals メソッドのオーバーライドと等価演算子のオーバーロードが推奨されています。
《参考》コード分析(FxCop)
CA1815: equals および operator equals を値型でオーバーライドします
[.NET] 値型 等価判定の思わぬ落とし穴(一般編)
[.NET] 値型 等価判定の思わぬ落とし穴(特殊編)
[.NET] 参照型 等価判定の思わぬ落とし穴(一般編)
[.NET] 参照型 等価判定の思わぬ落とし穴(特殊編)