Какая проблема делает IstructuraleQuatable и IstructuralComparable?
-
25-09-2019 - |
Вопрос
Я заметил эти два интерфейса, и несколько связанных классов были добавлены в .NET 4. Они кажутся немного лишними для меня; Я прочитал несколько блогов о них, но я до сих пор не могу понять, какую проблему они решают, это было сложно перед .NET 4.
Что используют IStructuralEquatable
а также IStructuralComparable
?
Решение
Все типы в .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
быть последовательным. В этом случае как бы вы поддерживаете как справочную, так и структурное равенство?
Другие советы
У меня такой же вопрос. Когда я бежал пример Любуна, я был удивлен, увидев, что у меня другой ответ! Несмотря на то, что этот ответ имеет 8 upvotes, это неправильно. После многих «отражателя», вот мой взять на себя.
Некоторые контейнеры (массивы, кортежи, анонимные типы) поддерживают 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
Интерфейс говорит (в разделе «Примечания»):
То
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()
C # в двух словах книга:
Потому что массив - это класс, массивы всегда (сами)
reference types
, независимо от типа элемента массива. Это означает, что утверждениеarrayB = arrayA
Приводит к двум переменным, которые ссылаются на одинаковый массив. Аналогичным образом, два отдельных массива всегда не пройдут испытание на равенство - если вы не используете Custom Several Comparer. 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