4

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

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

我发现事实上,即使 Guid 相同,比较也总是返回 false。

这是什么原因?

4

5 回答 5

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,但不是同一个对象。

如果您真的希望您的“不同”扩展方法不绑定到特定类型(如 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))));
} 
于 2012-07-05T05:50:29.813 回答
2
bool result = (guid1==guid2); //result -> true

您可以尝试在 myfunction 和 myfunction1 中将返回类型 Object 更改为 GUID

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

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

于 2012-07-05T05:46:58.090 回答
1

改用

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

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

Func<MyObject, object>

Guid 返回的 GuidmyFunctionmyFunction1被装在两个不同的对象中。请参阅此处了解 .NET 中的装箱和拆箱功能

因此,当比较完成时,将比较两个不同的对象。

in object的默认实现Equals是做引用相等检查。它不检查装箱值。有关如何实现 object.Equals 的更多详细信息,请参见此处

于 2012-07-05T05:47:59.887 回答
1

正如其他人所说,您的compareFieldPredicate返回 anobject及其运算符== 使用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.ExceptLINQ 方法实现。

您可以Enumerable.Except通过提供以下实现来重写您的实现IEqualityComparer

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));
}
于 2012-07-05T07:12:21.897 回答
0

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

Func<MyObject, Guid> myFunction = o => o.ID;
Func<MyObject, Guid> myFunction1 = o => o.ID;
于 2012-07-05T05:49:36.203 回答