我注意到.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