Object.Equalsメソッドを使うことで二つのオブジェクトが等しいかどうか単純な比較を行うことが出来ますが、どのような時に等しいとみなすか定義することが必要になる場合があります。
ここでは、オブジェクトの等価性を定義したり比較するためのインターフェイスであるIEquatable・IEqualityComparerと、関連するクラスについて見ていきます。 また、等価演算子のオーバーロードとこれらのインターフェイスの実装についても触れています。
Object.Equals
まずは、Object.Equalsメソッドとオブジェクトの等価性について見ていきます。 Equalsメソッドは現在のインスタンスと引数で指定されたオブジェクトとの等価性を比較するメソッドですが、Equalsメソッドをオーバーライドしない場合のデフォルトの動作は次のようになっています。 (値型と参照型 §.同値性・同一性の比較)
- 値型(struct)の場合
- 二つの値型オブジェクトがビット単位で等しい場合にtrue、そうでなければfalseを返す
- 参照型(class)の場合
- 二つの参照型オブジェクトが同一のオブジェクト(=参照先のオブジェクトが同一)である場合にtrue、そうでなければfalseを返す
そのため、次の例ではAccountクラスはEqualsメソッドをオーバーライドしていないため、各フィールドが同じ値を持つインスタンスでも異なるインスタンスの場合である限り等しいとは判断されません。 これにより、Array.IndexOfメソッドによる検索でも配列内には同一のインスタンスは存在しないため-1が返されます。
Equalsメソッドをオーバーライドすることで、どのような場合にオブジェクトが等しいとするかを定義することが出来ます。 次の例は、上記の例を書き換えEqualsメソッドをオーバーライドするようにしたものです。
結果を見て分かる通り、二つの異なるインスタンスでも各フィールドの値が等しければ、二つのインスタンスは等しいと扱われるようになりました。 また、これにより、Array.IndexOfメソッドによる検索でも配列内にある等しいインスタンスのインデックスが返されるようになりました。
なお、Equalsメソッドをオーバーライドする場合はObject.GetHashCodeメソッドもオーバーライドする必要があります。 そのため、上記の例をコンパイルすると警告(CS0659)が出ます。 GetHashCodeが返す値は値型では特に重要になりますが、ここでは実装を省略しています。 詳しくはGetHashCodeメソッドの解説を参照してください。
IEquatable<T>
IEquatable<T>インターフェイス(System名前空間)は、型パラメータTで指定された型との等価性の比較が可能であることを表すインターフェイスです。 Object.Equalsメソッドがobject型の引数を取るのに対し、IEquatable<T>.Equalsメソッドでは型Tを引数に取ります。 このため、Object.Equalsメソッドをオーバーライドする場合とは異なり、型チェックの必要が無くなります。
以下の例は、先の例をIEquatable<T>インターフェイスを実装したものに書き換えたものです。
単純にAccount.Equalsメソッドを呼び出した比較は正しく動作していますが、結果を見て分かる通りArray.IndexOfメソッドの結果は意図に反して-1を返しています。 これは、Array.IndexOfメソッドではオブジェクトがIEquatable<T>.Equalsメソッドを実装しているかどうかに関わらず、常にObject.Equalsメソッドを使って等価性の比較を行うためです。 AccountクラスはObject.Equalsをオーバーライドしていないため、このような結果となります。 objectにキャストしてからEqualsメソッドで比較した場合も同様に、意図に反してfalseを返しています。
このように、型がIEquatable<T>を実装していても、比較する側がIEquatable<T>.Equalsメソッドを呼び出すようになっていない限り、意図しない結果となります。 そのため、多くの場合はIEquatable<T>を実装すると同時に、Object.Equalsメソッドもオーバーライドすることになります。 以下の例は、上記の例にObject.Equalsメソッドのオーバーライドを追加したものです。
IEqualityComparer, IEqualityComparer<T>
IEqualityComparerインターフェイス(System.Collections名前空間)とIEqualityComparer<T>インターフェイス(System.Collections.Generic名前空間)は、等価性の比較処理を提供するためのインターフェイスです。 IEquatableとIEqualityComparerの関係はIComparableとIComparerの関係に似ていて、IEquatableではインターフェイスを実装する型に比較処理を実装するのに対し、IEqualityComparerでは比較される型とは別に比較処理を実装することが出来ます。
Hashtableはキーの比較にObject.Equals、DictionaryはObject.EqualsまたはIEquatable<T>.Equalsをデフォルトで使用しますが、コンストラクタでキーの比較時に使用するIEqualityComparer・IEqualityComparer<T>を指定することが出来ます これにより、キーとなる型でこれらを実装・オーバーライドする代わりに指定したIEqualityComparer・IEqualityComparer<T>を用いてキーの比較処理を行わせるようにすることが出来ます。
インターフェイスの詳細と実装例は省略します。 次に解説するEqualityComparer<T>クラスを参照してください。
EqualityComparer<T>
EqualityComparer<T>クラス(System.Collections.Generic名前空間)は、IEqualityComparer非ジェネリックインターフェイスとIEqualityComparer<T>ジェネリックインターフェイスを実装する抽象クラスです。 Comparer<T>クラスと同様、IEqualityCompareとIEqualityComparer<T>の二つのインターフェイスを実装するクラスを作成しなくても、このクラスを継承して抽象メソッドであるEqualsメソッドとGetHashCodeメソッドを実装するだけで同じ機能を提供することができるようになります。
EqualityComparer<T>.EqualsメソッドはT型の引数を二つ取り、等しい場合はtrue、そうでない場合はfalseを返すように実装します。 EqualityComparer<T>.GetHashCodeメソッドはT型の引数を一つ取り、引数で指定されたオブジェクトのハッシュ値を返すように実装します。 なお、二つのオブジェクトxとyについて、Equalsメソッドがtrueとなる場合はGetHashCodeメソッドがxとyに対して同じ値を返すように実装しなければなりません。
以下の例では、EqualityComparer<T>を継承したクラスを作成し、Dictionaryでのキーの比較に使用しています。 AccountクラスではIEquatable<T>やEqualsメソッドをオーバーライドしていない点に注目してください。
この例ではジェネリックなコレクションであるDictionaryを使っていますが、非ジェネリックなコレクションであるHashtableを使ってもDictionaryと同様に動作します。
EqualityComparer<T>のその他の使用例としてはプロパティ §.プロパティ変更の通知 (INotifyPropertyChanged)の例もご覧ください。
StringComparer
既に解説したStringComparerクラスは、IComparer・IComparer<string>だけでなくIEqualityComparer・IEqualityComparer<string>も実装しています。 以下の例では、StringComparerを使って大文字小文字を無視するDictionaryを作成しています。 StringComparerの詳細については、StringComparerについての個別の解説を参照してください。