正確には何がIEquatable<T>
あなたを買いますか?それが有用であることがわかる唯一の理由は、ジェネリック型を作成し、ユーザーに適切なequalsメソッドの実装と記述を強制する場合です。
私は何が欠けていますか?
正確には何がIEquatable<T>
あなたを買いますか?それが有用であることがわかる唯一の理由は、ジェネリック型を作成し、ユーザーに適切なequalsメソッドの実装と記述を強制する場合です。
私は何が欠けていますか?
MSDNから:
この
IEquatable(T)
インターフェイスは、、などの汎用コレクションオブジェクトによって使用され、、、、、などDictionary(TKey, TValue)
のメソッドで同等性List(T)
をLinkedList(T)
テストするときに使用されます。Contains
IndexOf
LastIndexOf
Remove
実装では、IEquatable<T>
これらのクラスに必要なキャストが1つ少なくなり、その結果、他のobject.Equals
方法で使用される標準のメソッドよりもわずかに高速になります。例として、2つのメソッドの異なる実装を参照してください。
public bool Equals(T other)
{
if (other == null)
return false;
return (this.Id == other.Id);
}
public override bool Equals(Object obj)
{
if (obj == null)
return false;
T tObj = obj as T; // The extra cast
if (tObj == null)
return false;
else
return this.Id == tObj.Id;
}
ここで最も重要な理由が言及されていないことに驚いています。
IEquatable<>
主に構造体のために導入された理由は次の2つです。
値型(構造体の読み取り)の場合、非ジェネリックEquals(object)
にはボクシングが必要です。IEquatable<>
構造体に強い型Equals
のメソッドを実装させて、ボクシングが不要になるようにします。
Object.Equals(Object)
構造体の場合、 (のオーバーライドされたバージョンである)のデフォルトの実装は、System.ValueType
リフレクションを使用して型のすべてのフィールドの値を比較することにより、値の同等性チェックを実行します。実装者が構造体の仮想Equalsメソッドをオーバーライドする場合、その目的は、値の同等性チェックを実行するより効率的な手段を提供し、オプションで構造体のフィールドまたはプロパティのサブセットに基づいて比較を行うことです。
どちらもパフォーマンスを向上させます。
参照型(読み取りクラス)はそれほどメリットがありません。実装により、IEquatable<>
からのキャストを回避できますSystem.Object
が、それは非常に些細な利点です。IEquatable<>
論理的に意図を明示的にするので、私はまだ私のクラスに実装されるのが好きです。
他の答えに加えて、ここに値型を実装するIEquatable<T>
(そして明らかにオーバーライドする)非常に良い理由があります。Equals(object)
それ以外の場合に呼び出されるデフォルトのValueType.Equals(object)
コードを見てください。これは、ボクシング、型評価を導入し、いずれかのフィールドが参照型である場合に最終的にリフレクションにフォールバックする絶対的なパフォーマンスキラーです。
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
RuntimeType type = (RuntimeType) base.GetType();
RuntimeType type2 = (RuntimeType) obj.GetType();
if (type2 != type)
{
return false;
}
object a = this;
if (CanCompareBits(this))
{
return FastEqualsCheck(a, obj);
}
FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
for (int i = 0; i < fields.Length; i++)
{
object obj3 = ((RtFieldInfo) fields[i]).UnsafeGetValue(a);
object obj4 = ((RtFieldInfo) fields[i]).UnsafeGetValue(obj);
if (obj3 == null)
{
if (obj4 != null)
{
return false;
}
}
else if (!obj3.Equals(obj4))
{
return false;
}
}
return true;
}
特定のシナリオ(辞書のキーとして値型を使用するなど)では、1回の不正な急降下でパフォーマンスが低下する可能性があります。
ドキュメントIEquality<T>
によると、パフォーマンスを向上させるために使用されます(ボクシングを防ぎます)。特にジェネリックコレクションで役立ちます。
IEquatable<T>
クラス階層に実装する場合は、次のパターンを使用できます。派生(兄弟を含む)クラスが等しくなるのを防ぎます。派生クラスに同等性が必要ない場合はスキップできますが、基本クラスと同等にIEquatable<Derived>
なるのを防ぐためにオーバーライドする必要がありますCanEqual
(もちろん、それらが同等であると見なされるべきでない場合)。
私はボクシングをしないことからの利益は持っているためのコストよりも少ないと思いますがCanEqual
。その場合、あなたはあなたのタイプを封印するべきであり、あなたはもはや必要ありませんCanEqual
。シーリングには、パフォーマンス上の利点もあります。
public class Base : IEquatable<Base>
{
protected virtual bool CanEqual(Base other) => other is Base;
public override bool Equals(object obj) => obj is Base other && Equals(other);
public bool Equals(Base other) => this.CanEqual(other) && other.CanEqual(this) /* && base logic */;
}
public class Derived : Base, IEquatable<Derived>
{
protected override bool CanEqual(Base other) => other is Derived;
public override bool Equals(object obj) => obj is Derived other && Equals(other);
public bool Equals(Derived other) => this.CanEqual(other) && other.CanEqual(this) /* && derived logic */;
}