Какая проблема решает проблему IStructuralEquatable и IStructuralComparable?

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

В чем используются IStructuralEquatable и IStructuralComparable?

Ответ 1

Все типы в .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, чтобы быть последовательным. В этом случае, как бы вы поддерживали как ссылочное, так и структурное равенство?

Ответ 2

У меня был тот же вопрос. Когда я запустил пример Л.Бушкина, я был удивлен, увидев, что у меня другой ответ! Несмотря на то, что у этого ответа есть 8 приоритетов, это неправильно. После многих "рефлекторных", вот мое занятие.

Некоторые контейнеры (массивы, кортежи, анонимные типы) поддерживают IStructuralComparable и IStructuralEquatable.

IStructuralComparable поддерживает глубокую сортировку по умолчанию.
IStructuralEquatable поддерживает глубокое хеширование по умолчанию.

{Обратите внимание, что EqualityComparer <T> поддерживает неглубокий (только один уровень контейнера), хеширование по умолчанию.}

Насколько я понимаю, это отображается только через класс 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

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

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

Ответ 4

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

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

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

Ответ 5

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()

введите описание изображения здесь

Ответ 6

С# в двух словах:

Поскольку Array является классом, массивы всегда являются (сами по себе) 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