【问题标题】:What problem does IStructuralEquatable and IStructuralComparable solve?IStructuralEquatable 和 IStructuralComparable 解决了什么问题?
【发布时间】:2011-04-06 07:21:45
【问题描述】:

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

IStructuralEquatableIStructuralComparable 有什么用?

【问题讨论】:

    标签: .net equality icomparable iequalitycomparer


    【解决方案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
    

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

    你没有问的一个问题是:

    为什么我们已经有了IStructuralComparableIStructuralEquatable 存在IComparableIEquatable 接口吗?

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

    【讨论】:

    • 您为什么不能自己指定一个 IEqualityComparer 来执行此操作? IStructuralEquatable 接口为此添加了什么?
    • @thecoop:有两个原因。首先,并非所有类型都实现接受IEqualityComparerEquals 重载——数组就是一个例子,IIRC。其次,提供一个相等比较器很好,但是如果你想表达某个方法需要两个可以在结构上进行比较的对象呢?在这种情况下能够指定IStructuralEquatable/IStructuralComparable 实际上很有用。在您想要应用此类比较的任何地方传递TupleComparerArrayComparer 也会很不方便。这两种方法并不相互排斥。
    • 这样的比较器与 Dictionary 和其他集合之类的东西有什么关系?我知道 Dictionary 似乎可以明智地处理结构,尽管在 .Net 2.0 中速度很慢; .Net 4.0(或 3.x)是否允许将数组方便地存储在 Dictionary 中(使用数组内容作为键)?
    • 太糟糕了 .NET 没有更好地定义相等性,并将两种类型的 Equals/GetHashCode 合并到框架中,X.EquivalentTo(Y) 意味着 X 引用的对象的所有成员将预期与YX.ValueEquals(Y) 引用的对象的所有行为等效,这意味着同时交换对XY所有 引用不会影响任何对象的行为任何一个的成员,除了与等价相关的哈希码。请注意,可以为 any 类型的对象评估这两个定义。请注意,基础Object.EquivalentTo...
    • 我相当肯定这个答案(和 cmets)是不准确的。 .NET确实支持两种不同版本的相等性:object.Equalsobject.ReferenceEqualsEquals 意在对给定类型最有意义的任何类型的比较进行覆盖,而 ReferenceEquals 不能被覆盖,并且始终通过引用进行比较。
    【解决方案2】:

    我也有同样的问题。当我运行 LBushkin 的示例时,我惊讶地发现我得到了不同的答案!即使这个答案有 8 个赞成票,它也是错误的。经过大量的“反思”,这是我的看法。

    某些容器(数组、元组、匿名类型)支持IStructuralComparableIStructuralEquatable

    • 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
    

    【讨论】:

      【解决方案3】:

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

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

      这个接口位于System.Collections 命名空间中这一事实也表明了这一点。

      【讨论】:

        【解决方案4】:

        C# in a nutshell书:

        因为 Array 是一个类,所以数组总是(本身)reference types,不管 数组的元素类型。这意味着语句arrayB = arrayA 结果 在引用相同数组的两个变量中。同样,两个不同的数组将 总是无法通过相等测试——除非您使用自定义相等比较器。框架 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
        

        【讨论】:

          【解决方案5】:

          F# 从 .net 4 开始使用它们。(.net 2 is here)

          这些接口对 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】:

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

            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
            

            【讨论】:

            • 顺便说一句,将泛型类型约束添加到您的 StructuralEqualityComparer 可能是个好主意。例如其中 T : IStructuralEquatable
            猜你喜欢
            • 2011-06-25
            • 1970-01-01
            • 2014-06-21
            • 2017-08-26
            • 2010-09-21
            • 1970-01-01
            • 2019-12-29
            • 2011-01-21
            • 1970-01-01
            相关资源
            最近更新 更多