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