Какую проблему решают IStructuralEquatable и IStructuralComparable? - PullRequest
54 голосов
/ 31 августа 2010

Я заметил, что эти два интерфейса и несколько связанных классов были добавлены в .NET 4. Они кажутся мне немного излишними;Я читал о них несколько блогов, но до сих пор не могу понять, какую проблему они решили, что было сложно до .NET 4.

Для чего нужны IStructuralEquatable и IStructuralComparable?

Ответы [ 6 ]

45 голосов
/ 31 августа 2010

Все типы в .NET поддерживают метод Object.Equals(), который по умолчанию сравнивает два типа для ссылочного равенства .Однако иногда также желательно иметь возможность сравнивать два типа для структурного равенства .

Лучший пример этого - массивы, в которых в .NET 4 теперь реализован интерфейс IStructuralEquatable.Это позволяет определить, сравниваете ли вы два массива для ссылочного равенства или для «структурного равенства» - имеют ли они одинаковое количество элементов с одинаковыми значениями в каждой позиции.Вот пример:

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

Другие типы, которые реализуют структурное равенство / сопоставимость, включают в себя кортежи и анонимные типы, которые явно выигрывают от возможности выполнять сравнение на основе их структуры и содержания.

Вопрос, который вы не задавали:

Почему у нас есть IStructuralComparable и IStructuralEquatable, когда уже существуют интерфейсы IComparable и IEquatable?

Ответ, который я хотел бы предложить, состоит в том, что в общем случае желательно проводить различие между эталонными и структурными сравнениями.Обычно ожидается, что если вы реализуете IEquatable<T>.Equals, вы также переопределите Object.Equals для обеспечения согласованности.В таком случае, как бы вы поддержали и ссылочное, и структурное равенство?

15 голосов
/ 09 апреля 2011

У меня был тот же вопрос.Когда я запускал пример Л.Бушкина, я был удивлен, увидев, что я получил другой ответ!Хотя этот ответ имеет 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
3 голосов
/ 14 июля 2011

Вот еще один пример, который иллюстрирует возможное использование двух интерфейсов:

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
2 голосов
/ 14 февраля 2016

Описание интерфейса IStructuralEquatable говорит (в разделе «Замечания»):

Интерфейс IStructuralEquatable позволяет вам выполнять пользовательские сравнения для проверки структурного равенства объектов коллекции .

Об этом также свидетельствует тот факт, что этот интерфейс находится в пространстве имен System.Collections.

0 голосов
/ 06 апреля 2019

C # в двух словах book:

Поскольку массив - это класс, массивы всегда (сами по себе) reference types, независимо от типа элемента массива.Это означает, что оператор arrayB = arrayA приводит к двум переменным, которые ссылаются на один и тот же массив.Точно так же два разных массива всегда не пройдут тест на равенство, если только вы не используете пользовательский компаратор равенства.Framework 4.0 представил один с целью сравнения элементов в массивах, к которым вы можете получить доступ через тип StructuralComparisons.

object[] a1 = { "string", 123, true};
object[] a2 = { "string", 123, true};

Console.WriteLine(a1 == a2);               // False
Console.WriteLine(a1.Equals(a2));          // False

IStructuralEquatable se1 = a1;
Console.WriteLine(se1.Equals(a2, StructuralComparisons.StructuralEqualityComparer));    // True
Console.WriteLine(StructuralComparisons.StructuralEqualityComparer.Equals(a1, a2));     // True

object[] a3 = {"string", 123, true};
object[] a4 = {"string", 123, true};
object[] a5 = {"string", 124, true};

IStructuralComparable se2 = a3;
Console.WriteLine(se2.CompareTo(a4, StructuralComparisons.StructuralComparer));    // 0
Console.WriteLine(StructuralComparisons.StructuralComparer.Compare(a3, a4));       // 0
Console.WriteLine(StructuralComparisons.StructuralComparer.Compare(a4, a5));       // -1
Console.WriteLine(StructuralComparisons.StructuralComparer.Compare(a5, a4));       // 1
0 голосов
/ 29 сентября 2017

F # начал использовать их с .net 4. ( .net 2 здесь )

Эти интерфейсы имеют решающее значение для F #

let list1 = [1;5;9] 
let list2 = List.append [1;5] [9]

printfn "are they equal? %b" (list1 = list2)

list1.GetType().GetInterfaces().Dump()

enter image description here

...