型「T」の等値比較では、次のメソッドをオーバーロードします。
int GetHashCode() //Overrides Object.GetHashCode
bool Equals(object other) //Overrides Object.Equals; would correspond to IEquatable, if such an interface existed
bool Equals(T other) //Implements IEquatable<T>; do this for each T you want to compare to
static bool operator ==(T x, T y)
static bool operator !=(T x, T y)
タイプ固有の比較コードは、タイプ セーフなIEquatable<T>
インターフェイス メソッドという 1 つの場所で実行する必要がありますEquals(T other)
。別の型 (T2) と比較する場合IEquatable<T2>
も同様に実装し、その型のフィールド比較コードを Equals(T2 other) に入れます。
オーバーロードされたすべてのメソッドと演算子は、等値比較タスクをメインのタイプ セーフな Equals(T other) インスタンス メソッドに転送する必要があります。これにより、クリーンな依存関係階層が維持され、各レベルでより厳密な保証が導入されて冗長性と不要な複雑さが排除されます。
bool Equals(object other)
{
if (other is T) //replicate this for each IEquatable<T2>, IEquatable<T3>, etc. you may implement
return Equals( (T)other) ); //forward to IEquatable<T> implementation
return false; //other is null or cannot be compared to this instance; therefore it is not equal
}
bool Equals(T other)
{
if ((object)other == null) //cast to object for reference equality comparison, or use object.ReferenceEquals
return false;
//if ((object)other == this) //possible performance boost, ONLY if object instance is frequently compared to itself! otherwise it's just an extra useless check
//return true;
return field1.Equals( other.field1 ) &&
field2.Equals( other.field2 ); //compare type fields to determine equality
}
public static bool operator ==( T x, T y )
{
if ((object)x != null) //cast to object for reference equality comparison, or use object.ReferenceEquals
return x.Equals( y ); //forward to type-safe Equals on non-null instance x
if ((object)y != null)
return false; //x was null, y is not null
return true; //both null
}
public static bool operator !=( T x, T y )
{
if ((object)x != null)
return !x.Equals( y ); //forward to type-safe Equals on non-null instance x
if ((object)y != null)
return true; //x was null, y is not null
return false; //both null
}
討論:
前の実装では、型固有の (つまり、フィールドの等価性) 比較を、IEquatable<T>
その型の実装の最後に集中させます。==
and演算子は、並列では!=
あるが反対の実装をしています。私は、依存するものに対して余分なメソッド呼び出しがあるように、一方が他方を参照するよりもこれを好みます。同等に機能するオペレーターを提供するのではなく、!=
オペレーターが単純にオペレーターを呼び出す場合は、余分なメソッド呼び出しを使用して回避することもできます。1. 場合によっては不要なオーバーヘッドが発生したり、2. インスタンスがそれ自体と他のインスタンスと比較される頻度に応じて一貫性のないパフォーマンスが発生したりする可能性があるため、自己との比較は equals 演算子と実装から除外されています。==
!(obj1 == obj2)
IEquatable<T>
私が好まないが言及すべき別の方法は、この設定を逆にして、代わりに等値演算子に型固有の等値コードを集中させ、それに依存する Equals メソッドを持たせることです。次に、次のショートカットを使用できますReferenceEquals(obj1,obj2)
Philip が以前の投稿で述べたように、参照の等価性と null の等価性を同時にチェックしますが、その考えは誤解を招くものです。1 つの石で 2 羽の鳥を殺しているように見えますが、実際にはより多くの作業を作成しています。オブジェクトが両方とも null でも同じインスタンスでもないことを確認した後、さらに、各インスタンスが無効です。私の実装では、1 つのインスタンスが null であることを 1 回だけチェックします。Equals インスタンス メソッドが呼び出されるまでに、比較される最初のオブジェクトが null である可能性は既に排除されているため、残りの作業は、もう一方が null であるかどうかを確認することだけです。そのため、最大 2 回の比較の後、使用する方法に関係なく、フィールド チェックに直接ジャンプします (Equals(object),Equals(T),==,!=
)。また、前述したように、ほとんどの場合、実際にそれ自体を比較して反対している場合は、フィールド比較に入る直前に Equals メソッドにそのチェックを追加できます。最後に追加するポイントは、すべてのレベルで冗長/無用なチェックを導入することなく、フロー/依存関係の階層を維持できることです。