12

これはこれに対するフォローアップの質問です: List<T>.Contains と T[].Contains の動作が異なります

T[].ContainsTクラスと構造体の場合、動作が異なります。この構造体があるとします:

public struct Animal : IEquatable<Animal>
{
    public string Name { get; set; }

    public bool Equals(Animal other) //<- he is the man
    {
        return Name == other.Name;
    }
    public override bool Equals(object obj)
    {
        return Equals((Animal)obj);
    }
    public override int GetHashCode()
    {
        return Name == null ? 0 : Name.GetHashCode();
    }
}

var animals = new[] { new Animal { Name = "Fred" } };

animals.Contains(new Animal { Name = "Fred" }); // calls Equals(Animal)

ここでは、予想どおりジェネリックEqualsが正しく呼び出されます。

しかし、クラスの場合:

public class Animal : IEquatable<Animal>
{
    public string Name { get; set; }

    public bool Equals(Animal other)
    {
        return Name == other.Name;
    }
    public override bool Equals(object obj) //<- he is the man
    {
        return Equals((Animal)obj);
    }
    public override int GetHashCode()
    {
        return Name == null ? 0 : Name.GetHashCode();
    }
}

var animals = new[] { new Animal { Name = "Fred" } };

animals.Contains(new Animal { Name = "Fred" }); // calls Equals(object)

非ジェネリックEqualsが呼び出され、`IEquatable を実装する利点が失われます。

両方のコレクションがジェネリックに見えるのに、との配列の呼び出しがEquals異なるのはなぜですか?struct[]class[]

配列の奇妙さは非常にイライラするので、完全に回避しようと考えています...

注:のジェネリック バージョンは、構造体が を実装Equalsする場合にのみ呼び出されます。型が を実装していない場合、またはであるかどうかに関係なく、 の非ジェネリック オーバーロードが呼び出されます。IEquatable<T>IEquatable<T>Equalsclassstruct

4

3 に答える 3

4

最終的に呼び出されるのは、実際には Array.IndexOf() ではないようです。そのソースを見ると、どちらの場合も Equals(object) が呼び出されると予想していました。Equals が呼び出された時点でスタック トレースを確認すると、表示されている動作が発生している理由がより明確になります (値型は Equals(Animal) を取得しますが、参照型は Equals(object) を取得します)。

値型 (struct Animal) のスタック トレースを次に示します。

at Animal.Equals(Animal other)
at System.Collections.Generic.GenericEqualityComparer`1.IndexOf(T[] array, T value, Int32 startIndex, Int32 count)
at System.Array.IndexOf[T](T[] array, T value, Int32 startIndex, Int32 count)
at System.Array.IndexOf[T](T[] array, T value)
at System.SZArrayHelper.Contains[T](T value)
at System.Linq.Enumerable.Contains[TSource](IEnumerable`1 source, TSource value) 

参照型 (オブジェクト Animal) のスタック トレースは次のとおりです。

at Animal.Equals(Object obj)
at System.Collections.Generic.ObjectEqualityComparer`1.IndexOf(T[] array, T value, Int32 startIndex, Int32 count)
at System.Array.IndexOf[T](T[] array, T value, Int32 startIndex, Int32 count)
at System.Array.IndexOf[T](T[] array, T value)
at System.SZArrayHelper.Contains[T](T value)
at System.Linq.Enumerable.Contains[TSource](IEnumerable`1 source, TSource value)

このことから、呼び出されているのは Array.IndexOf ではなく、Array.IndexOf[T] であることがわかります。その方法、等価比較子を使用することになります。参照型の場合は、Equals(object) を呼び出す ObjectEqualityComparer を使用します。値型の場合、おそらく高価なボクシングを避けるために、Equals(Animal) を呼び出す GenericEqualityComparer を使用します。

http://www.dotnetframework.orgでIEnumerable のソース コードを見ると 、上部に次のような興味深い部分があります。

// Note that T[] : IList<t>, and we want to ensure that if you use
// IList<yourvaluetype>, we ensure a YourValueType[] can be used
// without jitting.  Hence the TypeDependencyAttribute on SZArrayHelper.
// This is a special hack internally though - see VM\compile.cpp.
// The same attribute is on IList<t> and ICollection<t>.
[TypeDependencyAttribute("System.SZArrayHelper")]

私は TypeDependencyAttribute に精通していませんが、コメントから、Array に特別な魔法が起こっているのではないかと思っています。これは、Array の IList.Contains を介して IndexOf の代わりに IndexOf[T] が呼び出される方法を説明している可能性があります。

于 2013-11-10T11:20:01.680 に答える
0

どちらも独自の基本実装を使用しているためだと思いますEquals

クラスObject.Equalsは、ID の等価性を実装Structsする inheritと、ValueType.Equals値の等価性を実装する inherit です。

于 2013-11-10T09:44:47.577 に答える
0

の主な目的はIEquatable<T>、ジェネリック構造体型との合理的に効率的な等価比較を可能にすることです。が値型の場合、前者は後者に必要なヒープ割り当てを回避することを除いて、はIEquatable<T>.Equals((T)x)まったく同じように動作するように意図されています。は構造体型であるという制約はなく、シールされたクラスはそれを使用することでパフォーマンスがわずかに向上する場合がありますが、クラス型は構造体型ほどそのインターフェイスから恩恵を受けることはできません。適切に作成されたクラスは、外部コードが代わりにEquals((object)(T)x);TIEquatable<T>TIEquatable<T>.Equals(T)Equals(Object)、しかしそれ以外の場合は、どの比較方法が使用されているかを気にする必要はありません。クラスで使用することのパフォーマンス上の利点IEquatable<T>は決して大きくないため、クラス型を使用していることを認識しているコードは、型がたまたま実装されているかどうかを確認するために必要な時間をIEquatable<T>、インターフェイスがもっともらしく提供できるパフォーマンスの向上によって回収できない可能性が高いと判断する場合があります。

ちなみに、X と Y が「通常の」クラスである場合、X または Y のいずれかが他方から派生した場合、X.Equals(Y) は正当に true になる可能性があることに注意してください。さらに、封印されていないクラス型の変数は、そのクラスがそのインターフェイスを実装しているかどうかに関係なく、正当に任意のインターフェイス型の 1 つと等しいと比較できます。Object比較すると、構造体は、それ自体の型、ValueType、または構造体自体が実装するインターフェースの変数と等しいかどうかのみを比較できます。クラス型インスタンスがより広い範囲の変数型と「等しい」可能性があるという事実はIEquatable<T>、構造体型ほど適用できないことを意味します。

PS - 配列が特別な理由はもう 1 つあります。配列は、クラスではできない共分散のスタイルをサポートしています。与えられた

Dog Fido = new Dog();
Cat Felix = new Cat();
Animal[] meows = new Cat[]{Felix};

テストすることは完全に合法meows.Contains(Fido)です。またはmeowsのインスタンスに置き換えられた場合、新しい配列には実際に;が含まれる可能性があります。そうでなかったとしても、正当に の未知の型の変数を持っていて、それが に含まれているかどうかを知りたいと思うかもしれません。が を実装していても、 の要素が に等しいかどうかをテストするメソッドを使用しようとすると、に変換できないため失敗します。システムが実行可能な場合とそうでない場合に使用する方法はあるかもしれませんが、それは多くの複雑さを追加し、単に.Animal[]Dog[]FidoAnimalmeowsCatIEquatable<Cat>IEquatable<Cat>.Equals(Cat)meowsFidoFidoCatIEquatable<Cat>Equals(Object)Equals(Object)

于 2013-11-11T19:26:40.173 に答える