IStructuralEquatable 和 IStructuralComparable 解决什么问题?

发布于 2024-09-16 20:51:35 字数 181 浏览 6 评论 0原文

我注意到 .NET 4 中添加了这两个接口以及几个相关的类。它们对我来说似乎有点多余;我读过几篇关于它们的博客,但我仍然不明白它们解决了哪些在 .NET 4 之前很棘手的问题。

IStructuralEquatableIStructuralComparable 有什么用?

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 still can't figure out what problem they solve that was tricky before .NET 4.

What use are IStructuralEquatable and IStructuralComparable?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

漫漫岁月 2024-09-23 20:51:35

.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.Equals,您也将重写 Object.Equals 以保持一致。在这种情况下,您将如何支持引用平等和结构平等?

All types in .NET support the Object.Equals() method which, by default, compares two types for reference equality. However, sometimes, it also desirable to be able to compare two types for structural equality.

The best example of this is arrays, which with .NET 4 now implement the IStructuralEquatable interface. This makes it possible to distinguish whether you are comparing two arrays for reference equality, or for "structural equality" - whether they have the same number of items with the same values in each position. Here's an example:

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

Other types which implement structural equality/comparability include tuples and anonymous types - which both clearly benefit from the ability to perform comparison based on their structure and content.

A question you didn't ask is:

Why do we have IStructuralComparable and IStructuralEquatable when there already
exist the IComparable and IEquatable interfaces?

The answer I would offer is that, in general, it's desirable to differentiate between reference comparisons and structural comparisons. It's normally expected that if you implement IEquatable<T>.Equals you will also override Object.Equals to be consistent. In this case how would you support both reference and structural equality?

微凉徒眸意 2024-09-23 20:51:35

我也有同样的问题。当我运行 LBushkin 的示例时,我惊讶地发现我得到了不同的答案!尽管这个答案有 8 票赞成,但它是错误的。经过多次“反思”,以下是我对事情的看法。

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

  • IStructuralComparable 支持深度默认排序。
  • IStructuralEquatable 支持深度默认哈希。

{请注意,EqualityComparer 支持浅层(仅 1 个容器级别)、默认哈希。}

据我所知,这仅通过 StructuralComparisons 类公开。我能想到的使其有用的唯一方法是创建一个 StructuralEqualityComparer 辅助类,如下所示:

    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

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<T> 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<T> helper class as follow:

    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 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<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 make our own container play 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 make a HashSet with items having containers within custom containers within 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));      //true
        Console.WriteLine(set.Add(item1Clone)); //false
        Console.WriteLine(set.Add(item2));      //true
沙沙粒小 2024-09-23 20:51:35

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

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

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

The description of the IStructuralEquatable Interface says (in the "Remarks" section):

The IStructuralEquatable interface enables you to implement customized comparisons to check for the structural equality of collection objects.

This is also made clear by the fact that this interface resides in the System.Collections namespace.

寄离 2024-09-23 20:51:35

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

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

Here is another example that illustrates a possible usage of the two interfaces:

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
过度放纵 2024-09-23 20:51:35

C# 简而言之一本书:

因为数组是一个类,所以数组始终(本身)引用类型,无论
数组的元素类型。这意味着语句 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

C# in a nutshell book:

Because Array is a class, arrays are always (themselves) reference types, regardless
of the array’s element type. This means that the statement arrayB = arrayA results
in two variables that reference the same array. Similarly, two distinct arrays will
always fail an equality test—unless you use a custom equality comparer. Framework
4.0 introduced one for the purpose of comparing elements in arrays which you can
access via the StructuralComparisons type.

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
瞎闹 2024-09-23 20:51:35

F# 从 .net 4 开始使用它们。 ( .net 2 在这里

这些接口对于 F# 至关重要

let list1 = [1;5;9] 
let list2 = List.append [1;5] [9]

printfn "are they equal? %b" (list1 = list2)

list1.GetType().GetInterfaces().Dump()

在此处输入图像描述

F# started using them since .net 4. ( .net 2 is here)

These interfaces are crucial to F#

let list1 = [1;5;9] 
let list2 = List.append [1;5] [9]

printfn "are they equal? %b" (list1 = list2)

list1.GetType().GetInterfaces().Dump()

enter image description here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文