我注意到.NET 4 中添加了这两个接口和几个相关的类。它们对我来说似乎有点多余;我已经阅读了几篇关于它们的博客,但我仍然无法弄清楚它们解决了哪些在 .NET 4 之前很棘手的问题。
IStructuralEquatable
和有什么用IStructuralComparable
?
我注意到.NET 4 中添加了这两个接口和几个相关的类。它们对我来说似乎有点多余;我已经阅读了几篇关于它们的博客,但我仍然无法弄清楚它们解决了哪些在 .NET 4 之前很棘手的问题。
IStructuralEquatable
和有什么用IStructuralComparable
?
.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
实现结构相等/可比性的其他类型包括元组和匿名类型——它们都明显受益于基于其结构和内容执行比较的能力。
你没有问的一个问题是:
为什么我们有
IStructuralComparable
以及IStructuralEquatable
何时已经存在IComparable
andIEquatable
接口?
我要提供的答案是,一般来说,最好区分参考比较和结构比较。通常预计,如果您实施IEquatable<T>.Equals
,您还将覆盖Object.Equals
以保持一致。在这种情况下,您将如何支持引用和结构平等?
我有同样的问题。当我运行 LBushkin 的示例时,我惊讶地发现我得到了不同的答案!即使这个答案有 8 个赞成票,它也是错误的。经过大量的“反思”,这是我对事情的看法。
某些容器(数组、元组、匿名类型)支持IStructuralComparable
和IStructuralEquatable
.
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
这是另一个示例,说明了这两个接口的可能用法:
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
IStructuralEquatable
接口的描述说(在“备注”部分):
该
IStructuralEquatable
接口使您能够实现自定义比较以检查集合对象的结构相等性。
System.Collections
这个接口驻留在名称空间中这一事实也清楚地表明了这一点。
reference types
因为 Array 是一个类,所以无论数组的元素类型如何,数组总是 (它们自己) 。这意味着该语句arrayB = arrayA
会产生两个引用同一数组的变量。同样,两个不同的数组总是无法通过相等测试——除非您使用自定义相等比较器。Framework 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
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()