62

これら 2 つのインターフェイスと、関連するいくつかのクラスが .NET 4 に追加されていることに気付きました。それらに関するいくつかのブログを読んだことがありますが、.NET 4 より前では困難だった、どのような問題を解決するのかまだわかりません。

IStructuralEquatableとは何に使うのIStructuralComparableですか?

4

6 に答える 6

54

.NET のすべての型はObject.Equals()、既定で 2 つの型を比較して参照が等しいかどうかを調べるメソッドをサポートしています。ただし、場合によっては、2 つのタイプの構造上の同等性を比較できることが望ましい場合もあります。

これの最も良い例は配列で、.NET 4 でIStructuralEquatableインターフェイスを実装するようになりました。これにより、2 つの配列を比較して参照が等しいか、「構造が等しい」かを区別できます。つまり、各位置に同じ値を持つ同じ数の項目があるかどうかです。次に例を示します。

int[] array1 = new int[] { 1, 5, 9 };
int[] array2 = new int[] { 1, 5, 9 };

// using reference comparison...
Console.WriteLine( array1.Equals( array2 ) ); // outputs false

// now using the System.Array implementation of IStructuralEquatable
Console.WriteLine(
    StructuralComparisons.StructuralEqualityComparer.Equals( array1, array2 )
); // outputs true

構造的等価性/比較可能性を実装するその他の型には、タプルと匿名型が含まれます。どちらも、構造と内容に基づいて比較を実行できるという利点があります。

あなたが尋ねていない質問は次のとおりです。

andインターフェイスが既に存在するのに、なぜIStructuralComparableandがあるのでしょうか。IStructuralEquatableIComparableIEquatable

私が提供する答えは、一般に、参照比較と構造比較を区別することが望ましいということです。通常、実装IEquatable<T>.Equalsする場合は、オーバーライドObject.Equalsして一貫性を保つことも期待されます。この場合、どのように参照と構造の同等性をサポートしますか?

于 2010-08-31T14:25:45.597 に答える
20

同じ質問がありました。LBushkin の例を実行したとき、別の答えが得られたことに驚きました。その答えには8つの賛成票がありますが、間違っています。多くの「リフレクター」の後、これが私の見解です。

特定のコンテナー (配列、タプル、匿名型) はIStructuralComparable、 およびIStructuralEquatable.

  • IStructuralComparable深いデフォルトの並べ替えをサポートします。
  • IStructuralEquatable深いデフォルトのハッシュをサポートします。

{EqualityComparer<T>浅い (1 つのコンテナー レベルのみ)、デフォルトのハッシュをサポートすることに注意してください。}

私が見る限り、これは StructuralComparisons クラスを通じてのみ公開されます。これを便利にする唯一の方法は、次のStructuralEqualityComparer<T>ようにヘルパー クラスを作成することです。

    public class StructuralEqualityComparer<T> : IEqualityComparer<T>
    {
        public bool Equals(T x, T y)
        {
            return StructuralComparisons.StructuralEqualityComparer.Equals(x,y);
        }

        public int GetHashCode(T obj)
        {
            return StructuralComparisons.StructuralEqualityComparer.GetHashCode(obj);
        }

        private static StructuralEqualityComparer<T> defaultComparer;
        public static StructuralEqualityComparer<T> Default
        {
            get
            {
                StructuralEqualityComparer<T> comparer = defaultComparer;
                if (comparer == null)
                {
                    comparer = new StructuralEqualityComparer<T>();
                    defaultComparer = comparer;
                }
                return comparer;
            }
        }
    }

これで、コンテナ内のコンテナ内にコンテナを持つアイテムで HashSet を作成できます。

        var item1 = Tuple.Create(1, new int[][] { new int[] { 1, 2 }, new int[] { 3 } });
        var item1Clone = Tuple.Create(1, new int[][] { new int[] { 1, 2 }, new int[] { 3 } });
        var item2 = Tuple.Create(1, new int[][] { new int[] { 1, 3 }, new int[] { 3 } });

        var set = new HashSet<Tuple<int, int[][]>>(StructuralEqualityComparer<Tuple<int, int[][]>>.Default);
        Console.WriteLine(set.Add(item1));      //true
        Console.WriteLine(set.Add(item1Clone)); //false
        Console.WriteLine(set.Add(item2));      //true

これらのインターフェースを実装することで、独自のコンテナを他のコンテナとうまく連携させることもできます。

public class StructuralLinkedList<T> : LinkedList<T>, IStructuralEquatable
    {
        public bool Equals(object other, IEqualityComparer comparer)
        {
            if (other == null)
                return false;

            StructuralLinkedList<T> otherList = other as StructuralLinkedList<T>;
            if (otherList == null)
                return false;

            using( var thisItem = this.GetEnumerator() )
            using (var otherItem = otherList.GetEnumerator())
            {
                while (true)
                {
                    bool thisDone = !thisItem.MoveNext();
                    bool otherDone = !otherItem.MoveNext();

                    if (thisDone && otherDone)
                        break;

                    if (thisDone || otherDone)
                        return false;

                    if (!comparer.Equals(thisItem.Current, otherItem.Current))
                        return false;
                }
            }

            return true;
        }

        public int GetHashCode(IEqualityComparer comparer)
        {
            var result = 0;
            foreach (var item in this)
                result = result * 31 + comparer.GetHashCode(item);

            return result;
        }

        public void Add(T item)
        {
            this.AddLast(item);
        }
    }

HashSetこれで、コンテナ内のカスタム コンテナ内にコンテナを持つアイテムで を作成できます。

        var item1 = Tuple.Create(1, new StructuralLinkedList<int[]> { new int[] { 1, 2 }, new int[] { 3 } });
        var item1Clone = Tuple.Create(1, new StructuralLinkedList<int[]> { new int[] { 1, 2 }, new int[] { 3 } });
        var item2 = Tuple.Create(1, new StructuralLinkedList<int[]> { new int[] { 1, 3 }, new int[] { 3 } });

        var set = new HashSet<Tuple<int, StructuralLinkedList<int[]>>>(StructuralEqualityComparer<Tuple<int, StructuralLinkedList<int[]>>>.Default);
        Console.WriteLine(set.Add(item1));      //true
        Console.WriteLine(set.Add(item1Clone)); //false
        Console.WriteLine(set.Add(item2));      //true
于 2011-04-08T21:42:26.470 に答える
5

次に、2 つのインターフェイスの可能な使用法を示す別の例を示します。

var a1 = new[] { 1, 33, 376, 4};
var a2 = new[] { 1, 33, 376, 4 };
var a3 = new[] { 2, 366, 12, 12};

Debug.WriteLine(a1.Equals(a2)); // False
Debug.WriteLine(StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2)); // True

Debug.WriteLine(StructuralComparisons.StructuralComparer.Compare(a1, a2)); // 0
Debug.WriteLine(StructuralComparisons.StructuralComparer.Compare(a1, a3)); // -1
于 2011-07-14T08:45:36.410 に答える