IStructuralEquatable和IStructuralComparable解决了什么问题?

the*_*oop 55 .net equality icomparable iequalitycomparer

我注意到这两个接口,以及几个相关的类,已经在.NET 4中添加了.它们对我来说似乎有点多余; 我已经阅读了几个关于它们的博客,但我仍然无法弄清楚它们在.NET 4之前解决了哪些棘手问题.

什么是使用IStructuralEquatableIStructuralComparable

LBu*_*kin 46

.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
Run Code Online (Sandbox Code Playgroud)

实现结构平等/可比性的其他类型包括元组和匿名类型 - 两者都明显受益于基于其结构和内容执行比较的能力.

你没问的问题是:

为什么我们有IStructuralComparableIStructuralEquatable什么时候已经存在IComparableIEquatable接口?

我要提出的答案是,一般来说,需要区分参考比较和结构比较.通常情况下,如果您实施,IEquatable<T>.Equals您也将覆盖Object.Equals以保持一致.在这种情况下,您如何支持参考和结构平等?

  • 我相当确定这个答案(和评论)是不准确的。.NET确实支持两种不同版本的相等性:“object.Equals”和“object.ReferenceEquals”。对于给定类型最有意义的任何类型的比较,都应该重写“Equals”,而“ReferenceEquals”则不能被重写,并且始终通过引用进行比较。 (4认同)
  • 你为什么不能自己指定一个`IEqualityComparer`呢?`IStructuralEquatable`接口添加了什么? (3认同)

jyo*_*ung 18

我有同样的问题.当我运行LBushkin的例子时,我惊讶地发现我得到了不同的答案!虽然这个答案有8个赞成票,但这是错误的.在经历了很多"反思"之后,这是我对事物的看法.

某些容器(数组,元组,匿名类型)支持IStructuralComparable和IStructuralEquatable.

IStructuralComparable支持深度,默认排序.
IStructuralEquatable支持深度默认哈希.

{注意EqualityComparer<T>支持浅(只有1个容器级别),默认散列.}

据我所知,这只是通过StructuralComparisons类公开的.我能弄清楚这个有用的唯一方法是创建一个StructuralEqualityComparer<T>helper类,如下所示:

    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;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

现在我们可以创建一个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
Run Code Online (Sandbox Code Playgroud)

我们还可以通过实现这些接口使我们自己的容器与这些其他容器很好地配合.

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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

现在我们可以在容器内的自定义容器中创建具有容器的项目的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
Run Code Online (Sandbox Code Playgroud)


Mar*_*ist 5

这是另一个示例,说明了这两个接口的可能用法:

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
Run Code Online (Sandbox Code Playgroud)


Oli*_*bes 5

IStructuralEquatable接口的描述说(在“备注”部分):

IStructuralEquatable接口使您能够实现自定义比较以检查集合对象的结构相等性。

这个接口驻留在System.Collections命名空间中这一事实也清楚地表明了这一点。