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

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

Какая польза от IStructuralEquatable и IStructuralComparable?


person thecoop    schedule 31.08.2010    source источник


Ответы (6)


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

person LBushkin    schedule 31.08.2010
comment
Почему вы просто не можете сами указать IEqualityComparer, который это делает? Что к этому добавляет IStructuralEquatable интерфейс? - person thecoop; 31.08.2010
comment
@thecoop: Есть две причины. Во-первых, не все типы реализуют перегрузку Equals, которая принимает IEqualityComparer - например, массив IIRC. Во-вторых, предоставление средства сравнения на равенство - это хорошо, но что, если вы хотите выразить тот факт, что для определенного метода требуются два объекта, которые можно структурно сравнить? Возможность указать _3 _ / _ 4_ в таких случаях действительно полезна. Также было бы неудобно передавать TupleComparer или ArrayComparer везде, где вы хотите применить этот тип сравнения. Эти два подхода не исключают друг друга. - person LBushkin; 31.08.2010
comment
Как такие компараторы соотносятся с такими вещами, как Словарь и другие коллекции? Я знаю, что Dictionary, кажется, разумно обрабатывает структуры, хотя и медленно, в .Net 2.0; позволяет ли .Net 4.0 (или 3.x, если на то пошло) удобно хранить массивы в словаре (используя содержимое массива в качестве ключа)? - person supercat; 31.08.2010
comment
@supercat: Я не верю, что в .NET 4 можно использовать массивы в качестве ключей легче, чем в предыдущих версиях. Насколько мне известно, ни Dictionary, ни другие классы коллекций с ключами не используют интерфейс IStructuralEquatable. Но это было бы достаточно легко проверить с помощью тестового кода. - person LBushkin; 31.08.2010
comment
См. stackoverflow.com/questions/5813113/ для версии, которая решает проблемы, указанные @CodeInChaos. - person E.Z. Hart; 06.09.2011
comment
Жаль, что .NET не лучше определила равенство и не включила два типа Equals / GetHashCode в Framework, где X.EquivalentTo(Y) означает, что все члены объекта, на который ссылается X, будут вести себя так же, как и все члены указанного объекта to by Y и X.ValueEquals(Y), что означает, что одновременная перестановка всех ссылок на X и Y не повлияет на поведение каких-либо членов любого из них, кроме связанного с эквивалентностью хэш-кода. Обратите внимание, что оба определения можно оценивать для объектов типа any. Обратите внимание, что base _7 _... - person supercat; 03.07.2013
comment
... должен проверять ссылочное равенство; base Object.ValueEquals должен возвращать True [замена всех ссылок на два экземпляра X и Y типа System.Object не окажет заметного влияния на какие-либо члены, кроме связанных с эквивалентностью GetHashCode], но сущности, состояния которых взаимодействуют с другими объектами должен переопределить его, чтобы проверить ссылочное равенство. - person supercat; 03.07.2013
comment
Я почти уверен, что этот ответ (и комментарии) неточны. .NET действительно поддерживает две разные версии равенства: object.Equals и object.ReferenceEquals. Equals предназначен для переопределения для любого вида сравнения, имеющего наибольший смысл для данного типа, тогда как ReferenceEquals не может быть переопределен и всегда сравнивается по ссылке. - person Zenexer; 30.08.2019
comment
это аналогичный вопрос stackoverflow.com/questions/9548222/ - person amjad; 03.03.2021
comment
@thecoop не уверен, что вы уже получили ответ, но текущий принятый ответ неверен, поскольку Zenexer указал, что вы можете использовать Object.ReferenceEquals () для проверки ссылочного равенства. дайте мне знать, если вас все еще интересует этот вопрос l. поэтому я могу подготовить ответ, объясняющий, как это работает, но он может быть длинным, и вам действительно нужно знать IEqualityComparer, чтобы понять, как работает `IStructuralEquatable` - person amjad; 03.03.2021

У меня такой же вопрос. Когда я запустил пример Л.Бушкина, то с удивлением увидел, что получил другой ответ! Несмотря на то, что у этого ответа 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
person jyoung    schedule 08.04.2011

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

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

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

person Olivier Jacot-Descombes    schedule 13.02.2016

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

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
person Marc Sigrist    schedule 14.07.2011
comment
Кстати, вероятно, было бы неплохо добавить ограничение общего типа в свой StructuralEqualityComparer. например где T: IStructuralEquatable - person AndrewS; 17.01.2014

Книга в двух словах о C #:

Поскольку 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
person Sina Lotfi    schedule 06.04.2019

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

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

person Rm558    schedule 29.09.2017