What problem does IStructuralEquatable and IStructuralComparable solve?

后端 未结 6 2086
没有蜡笔的小新
没有蜡笔的小新 2020-11-27 15:32

I\'ve noticed these two interfaces, and several associated classes, have been added in .NET 4. They seem a bit superfluous to me; I\'ve read several blogs about them, but I

6条回答
  •  抹茶落季
    2020-11-27 15:59

    I had the same question. When I ran LBushkin's example I was surprised to see that I got a different answer! Even though that answer has 8 upvotes, it is wrong. After a lot of 'reflector'ing, here is my take on things.

    Certain containers (arrays, tuples, anonymous types) support IStructuralComparable and IStructuralEquatable.

    • IStructuralComparable supports deep, default sorting.
    • IStructuralEquatable supports deep, default hashing.

    {Note that EqualityComparer supports shallow (only 1 container level), default hashing.}

    As far as I see this is only exposed through the StructuralComparisons class. The only way I can figure out to make this useful is to make a StructuralEqualityComparer helper class as follow:

        public class StructuralEqualityComparer : IEqualityComparer
        {
            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 defaultComparer;
            public static StructuralEqualityComparer Default
            {
                get
                {
                    StructuralEqualityComparer comparer = defaultComparer;
                    if (comparer == null)
                    {
                        comparer = new StructuralEqualityComparer();
                        defaultComparer = comparer;
                    }
                    return comparer;
                }
            }
        }
    

    Now we can make a HashSet with items having containers within containers within 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>(StructuralEqualityComparer>.Default);
            Console.WriteLine(set.Add(item1));      //true
            Console.WriteLine(set.Add(item1Clone)); //false
            Console.WriteLine(set.Add(item2));      //true
    

    We can also make our own container play well with these other containers by implementing these interfaces.

    public class StructuralLinkedList : LinkedList, IStructuralEquatable
        {
            public bool Equals(object other, IEqualityComparer comparer)
            {
                if (other == null)
                    return false;
    
                StructuralLinkedList otherList = other as StructuralLinkedList;
                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 make a HashSet with items having containers within custom containers within containers.

            var item1 = Tuple.Create(1, new StructuralLinkedList { new int[] { 1, 2 }, new int[] { 3 } });
            var item1Clone = Tuple.Create(1, new StructuralLinkedList { new int[] { 1, 2 }, new int[] { 3 } });
            var item2 = Tuple.Create(1, new StructuralLinkedList { new int[] { 1, 3 }, new int[] { 3 } });
    
            var set = new HashSet>>(StructuralEqualityComparer>>.Default);
            Console.WriteLine(set.Add(item1));      //true
            Console.WriteLine(set.Add(item1Clone)); //false
            Console.WriteLine(set.Add(item2));      //true
    

提交回复
热议问题