【问题标题】:Unexpected behaviour when comparing GUIDs in .NET在 .NET 中比较 GUID 时出现意外行为
【发布时间】:2012-07-05 05:40:42
【问题描述】:

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

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。

这是什么原因?

【问题讨论】:

  • 包含 guid 类型的字段 ID 的自定义类。抱歉,如果不清楚...
  • 也许我遗漏了一些东西,但如果它是一个类型,你为什么要把它传递给 myFunctionmyFunction1

标签: c# lambda comparison extension-methods guid


【解决方案1】:

您的问题是您在比较它们之前将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))));
} 

【讨论】:

  • 非常感谢您的简洁回答。我认为这可能是这些方面的东西,但没有 100% 理解它。顺便说一句,Equatable 通用约束是一个非常好的触摸。
【解决方案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。

【讨论】:

    【解决方案3】:

    改用

    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

    【讨论】:

    • 这就是我要表达我的答案的方式,但是由于两个返回的对象是相同的引用,所以我不清楚它为什么返回 false。但是,将它们更改为 Guid 仍然可以解决问题。
    • @Michael myFunctionmyFunction1 返回的 Guid 将被装箱在两个不同的对象中。看到这个链接msdn.microsoft.com/en-us/library/yz2be5wk.aspx
    【解决方案4】:

    正如其他人所说,您的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));
    }
    

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        猜你喜欢
        • 2014-10-11
        • 2014-03-13
        • 2014-01-21
        • 1970-01-01
        • 2012-08-25
        • 2015-03-26
        • 1970-01-01
        • 1970-01-01
        • 2011-11-09
        相关资源
        最近更新 更多