Какую проблему решает IStructuralEquatable и IStructuralComparable?
Я заметил, что эти два интерфейса и несколько связанных классов были добавлены в .NET 4. Они кажутся мне немного лишними; я прочитал несколько блогов о них, но я все еще не могу понять, какую проблему они решают, что было сложно до .NET 4.
зачем IStructuralEquatable
и IStructuralComparable
?
5 ответов:
все типы в .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
чтобы быть последовательным. В этом случае как бы вы поддержали как ссылочное, так и структурное равенство?
у меня был тот же вопрос. Когда я запустил пример 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; } } }
теперь мы можем сделать хэш-набор с элементами, имеющими контейнеры внутри контейнеров внутри контейнеров.
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); } }
теперь мы можем сделать хэш-набор с элементами, имеющими контейнеры внутри пользовательских контейнеров внутри контейнеров.
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
вот еще один пример, который иллюстрирует использование двух интерфейсов:
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
в описании
IStructuralEquatable
интерфейс Microsoft четко говорит (в разделе "Примечания"):The
IStructuralEquatable
интерфейс позволяет реализовать индивидуальные сравнения для проверки структурного равенства объекты коллекции.Это также ясно из того факта, что этот интерфейс находится в
System.Collections
пространство имен.
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()