比较.NET中的GUID时出现意外行为

时间:2012-07-05 05:40:42

标签: c# lambda comparison extension-methods guid

我试图创建一个看起来像这样的扩展方法......

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。

这是什么原因?

5 个答案:

答案 0 :(得分:7)

你的问题是你在比较之前将Guids装入对象。请考虑以下代码:

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

Console.WriteLine(g1 == g2);

object o1 = g1;
object o2 = g2;

Console.WriteLine(o1 == o2);

实际输出:

true
false

由于“o1”和“o2”,虽然等于同一个Guid,但不是同一个对象。

如果你真的希望你的“Distinct”扩展方法不依赖于特定类型(如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))));
} 

答案 1 :(得分:2)

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

您可以尝试在myfunction和myfunction1

中将返回类型对象更改为GUID
Func<MyObject, Guid> myFunction = o => o.ID;
Func<MyObject, Guid> myFunction1 = o => o.ID;

否则,将返回值(true)装箱为Object,并检查Reference equality,即false。

答案 2 :(得分:1)

更改使用

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

这是因为你的功能被定义为

Func<MyObject, object>

myFunctionmyFunction1返回的Guid将装在两个不同的对象中。有关.NET中的装箱和拆箱功能,请参阅here

因此,在进行比较时,会比较两个不同的对象。

对象中Equals的默认实现是进行引用相等性检查。它不会检查盒装值。有关object.Equals如何实现的详细信息,请参阅here

答案 3 :(得分:0)

如果更改lambda以返回Guid,那么它可以工作:

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

答案 4 :(得分: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));
}