我注意到.NET 4中增加了这两个接口和几个相关的类,它们对我来说似乎有些多余。我读过关于他们的几个博客,但我仍然无法弄清楚他们解决,这是.NET 4IStructuralEquatable和IStructuralComparable能解决什么问题?
面前棘手的问题是什么有什么用处IStructuralEquatable
和IStructuralComparable
?
我注意到.NET 4中增加了这两个接口和几个相关的类,它们对我来说似乎有些多余。我读过关于他们的几个博客,但我仍然无法弄清楚他们解决,这是.NET 4IStructuralEquatable和IStructuralComparable能解决什么问题?
面前棘手的问题是什么有什么用处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
和IEquatable
接口?
我想提供的答案是,总的来说,有必要区分参考比较和结构比较。通常预期如果您实施IEquatable<T>.Equals
,您也将覆盖Object.Equals
以保持一致。在这种情况下,你会如何支持参考和结构平等?
我有同样的问题。当我运行LBushkin的例子时,我惊讶地发现我得到了不同的答案!即使这个答案有8个upvotes,这是错误的。经过很多'反射镜'之后,这里是我的东西。 (数组,元组,匿名类型)支持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
顺便说一句,向您的StructuralEqualityComparer添加一个泛型类型约束可能是一个好主意。例如其中T:IStructuralEquatable – AndrewS 2014-01-17 15:04:07
在IStructuralEquatable
Interface微软的描述清楚地说(“备注”一节中):
IStructuralEquatable
界面使您能够执行自定义比较来检查收集对象的结构相同性。
这也通过这个接口驻留在System.Collections
命名空间的事实表明了这一点。
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()
为什么你就不能指定'IEqualityComparer'自己,做这个?什么'IStructuralEquatable'接口添加到此? – thecoop 2010-08-31 14:39:10
@thecoop:有两个原因。首先,并非所有类型都实现接受“IEqualityComparer”的“Equals”的重载 - 数组是IIRC的一个示例。其次,提供一个相等比较器是很好的,但是如果你想表达一个事实,即某个方法需要两个可以在结构上进行比较的对象呢?在这种情况下能够指定'IStructuralEquatable' /'IStructuralComparable'实际上是有用的。在想要应用这种类型的比较的任何地方传递“TupleComparer”或“ArrayComparer”也是不方便的。这两种方法不是相互排斥的。 – LBushkin 2010-08-31 14:49:59
这些比较器如何与Dictionary和其他集合相关?我知道“词典”似乎在.Net 2.0中明显缓慢地处理结构; .Net 4.0(或者3.x)允许将数组方便地存储在Dictionary中(使用数组内容作为键)? – supercat 2010-08-31 15:13:01