Какая проблема решает проблему 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