I had the same question. When I launched the example of L. Bushkin, I was surprised to see that I have a different answer! Despite the fact that this answer has 8 priorities, this is incorrect. After many "reflex", here is my lesson.
Some containers (arrays, tuples, anonymous types) support IStructuralComparable and IStructuralEquatable.
IStructuralComparable supports deep sorting by default.
IStructuralEquatable supports deep hashing by default.
{Note that EqualityComparer <T> supports shallow (only one container level), hashing by default.}
As far as I understand, this is displayed only through the StructuralComparisons class. The only way I can do this is to make the helper class StructuralEqualityComparer<T> as follows:
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; } } }
Now we can create a HashSet with elements containing containers in containers inside containers.
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
We can also do our own container well with these other containers by implementing these interfaces.
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); } }
Now we can create a HashSet with elements containing containers in custom containers in containers.
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));