2012-07-05 87 views
1

我試圖創建一個看起來像這樣的擴展方法比較GUID的時候......意外的行爲在.NET

public static IEnumerable<T> Distinct<T>(this IEnumerable<T> value, IEnumerable<T> compareTo, Func<T, object> compareFieldPredicate) 
{ 
    return value.Where(o => !compareTo.Exists(p => compareFieldPredicate.Invoke(p) == compareFieldPredicate.Invoke(o))); 
} 

的想法是,我將能夠做這樣的事......

IEnumerable<MyCollection> distinctValues = MyCollection.Distinct(MyOtherCollection, o => o.ID); //Note that o.ID is a guid 

現在,在這一點上,我希望只有我不同的物品返回給我,但我發現這是從來沒有的情況。

進一步的研究使用下面的代碼來分解這個方法。

Guid guid1 = Guid.NewGuid(); 
Guid guid2 = new Guid(guid1.ToString()); 

Func<MyObject, object> myFunction = o => o.ID; 
Func<MyObject, object> myFunction1 = o => o.ID; 

bool result = myFunction(MyObject) == myFunction1(MyObject); 
//result = false 

我發現事實上,即使Guids是相同的比較將始終返回false。

是什麼原因呢?

+0

什麼'MyObject'? – reuben

+0

包含類型爲guid的字段ID的自定義類。對不起,如果這不清楚... –

+0

也許我錯過了一些東西,但如果它是一種類型,爲什麼你將它傳遞到'myFunction'或'myFunction1'? – reuben

回答

7

您的問題是,你拳擊的GUID到對象中你比較他們。考慮下面的代碼:

Guid g1 = Guid.NewGuid(); 
var g2 = g1; 

Console.WriteLine(g1 == g2); 

object o1 = g1; 
object o2 = g2; 

Console.WriteLine(o1 == o2); 

這實際上輸出:

true 
false 

由於 「01」 和 「O2」,而等於相同的GUID,是不一樣的物件。

如果你真的想你的「另類」的擴展方法不依賴於特定的類型(如GUID),你可以這樣做:

public static IEnumerable<TItem> Distinct<TItem, TProp>(this IEnumerable<TItem> value, IEnumerable<TItem> compareTo, Func<TItem, TProp> compareFieldPredicate) 
    where TProp : IEquatable<TProp> 
{ 
    return value.Where(o => !compareTo.Any(p => compareFieldPredicate(p).Equals(compareFieldPredicate(o)))); 
} 
+0

非常感謝你的簡潔的答案。我認爲這可能是沿着這些路線的東西,但並沒有100%理解它。等值泛型約束是一個非常好的觸摸順便說一句。 –

2
bool result = (guid1==guid2); //result -> true 

你可以嘗試返回Object類型更改爲GUID中的MyFunction和myfunction1

Func<MyObject, Guid> myFunction = o => o.ID; 
Func<MyObject, Guid> myFunction1 = o => o.ID; 

否則,返回值(true)被裝箱爲對象,參考平等檢查,這是假的。

1

更改使用

Func<MyObject, Guid> myFunction = o => o.ID; 
Func<MyObject, Guid> myFunction1 = o => o.ID; 

這是因爲你的函數定義爲

Func<MyObject, object> 

的GUID返回由myFunctionmyFunction1將在兩個不同的obejcts被裝箱。請參閱here用於.NET中的裝箱和拆箱功能。

因此,比較完成後,會比較兩個不同的對象。

對象中Equals的默認實現是做參考相等性檢查。它不檢查盒裝值。有關如何實現object.Equals的更多詳細信息,請參見here

+0

這就是我如何說出我的答案,但由於兩個返回的對象是相同的參考,所以我不清楚它爲什麼返回false。但是,將它們改爲Guid可以修復它。 – Michael

+0

@Michael由'myFunction'和'myFunction1'返回的Guid將被裝箱在兩個不同的對象中。看到這個鏈接http://msdn.microsoft.com/en-us/library/yz2be5wk.aspx –

0

如果更改拉姆達返回一個GUID,那麼它的工作原理:

Func<MyObject, Guid> myFunction = o => o.ID; 
Func<MyObject, Guid> myFunction1 = o => o.ID; 
0

正如其他人所說,你compareFieldPredicate返回object和其運營商==使用object.ReferenceEquals,而不是object.Equals,因此您的代碼始終檢查對象身份而不是相等。

一種解決方法是使用,而不是運營商==object.Equals方法:

public static IEnumerable<T> Distinct<T>(
    this IEnumerable<T> value, 
    IEnumerable<T> compareTo, 
    Func<T, object> compareFieldPredicate 
) 
{ 
    return value.Where(o => !compareTo.Exists(
     p => object.Equals(compareFieldPredicate(p), compareFieldPredicate(o)) 
    )); 
} 

一個更好的解決方案使用的實際密鑰類型默認的比較,消除拳擊,如果類型實現了IEquatable接口本身:

public static IEnumerable<T> Distinct<T, TKey>(
    this IEnumerable<T> value, 
    IEnumerable<T> compareTo, 
    Func<T, TKey> compareFieldPredicate 
) 
{ 
    return value.Where(o => !compareTo.Exists(
     p => EqualityComparer<TKey>.Default.Equals(compareFieldPredicate(p), compareFieldPredicate(o)) 
    )); 
} 

然而,大部分的Distinct方法的功能已經被
實施Enumerable.Except LINQ方法。

可以通過提供的IEqualityComparer實現重寫的Enumerable.Except方面的實現:

private class KeyEqualityComparer<T, TKey> : IEqualityComparer<T> 
{ 
    private readonly Func<T, TKey> _keySelector; 

    public KeyEqualityComparer(Func<T, TKey> keySelector) 
    { _keySelector = keySelector; } 

    public int GetHashCode(T item) 
    { return _keySelector(item).GetHashCode(); } 

    public bool Equals(T x, T y) 
    { return EqualityComparer<TKey>.Default.Equals(_keySelector(x), _keySelector(y)); } 
} 

public static IEnumerable<T> ExceptBy<T, TKey>(
    this IEnumerable<T> first, 
    IEnumerable<T> second, 
    Func<T, TKey> keySelector 
) 
{ 
    return first.Except(second, new KeyEqualityComparer<T, TKey>(keySelector)); 
}