2012-07-05 3 views
1

Я попытался создать метод расширения, который выглядит следующим образом ...Неожиданное поведение при сравнении GUIDs в .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))); 
} 

Идея заключается в том, что я мог бы сделать что-то вроде этого ...

Теперь на этом этапе я ожидал вернуть только мои отдельные элементы, но я обнаружил, что это не так.

При дальнейших исследованиях, разрушая этот метод, используя следующий код.

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 

Я нашел этот факт, даже если Гиды совпадают, сравнение всегда будет возвращать false.

В чем причина этого?

+0

Что 'MyObject'? – reuben

+0

Пользовательский класс, который содержит идентификатор поля типа guid. Извините, если это was not clear ... –

+0

Возможно, я что-то упустил, но если это тип, почему вы передаете его в 'myFunction' или' myFunction1'? – reuben

ответ

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)))); 
} 
+0

Большое спасибо за краткий ответ. Я подумал, что это может быть что-то в этом роде, но не понял его на 100%. Уравновешенное общее ограничение - это действительно приятное прикосновение BTW. –

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

Вы можете попытаться изменить тип возвращаемого объекта в GUID в MyFunction и myfunction1

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

В противном случае, возвращаемое значение (истина) упаковывается в объект, и равенство ссылок проверяется, что неверно ,

1

Изменить использовать

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

Это потому, что ваша функция была определена как

Func<MyObject, object> 

Guid возвращаемого myFunction и myFunction1 будет зажат в двух разных obejcts. См. here для функции бокса и распаковки в .NET.

Поэтому, когда сравнение было выполнено, сравниваются два разных объекта.

По умолчанию реализация Equals в объекте выполняет проверку равенства ссылок. Он не проверяет значения в штучной упаковке. См. here для получения дополнительной информации о том, как реализуется объект.

+0

Вот как я собирался рассказать о своем ответе, но поскольку два возвращаемых объекта - это одна и та же ссылка, мне не ясно, почему он возвращает false. Тем не менее, изменение их в Guid исправляет его в любом случае. – Michael

+0

@Michael The Guid, возвращенный 'myFunction' и' myFunction1', будет помещен в два разных объекта. См. Эту ссылку 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.

Вы можете переписать реализацию в терминах 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)); 
} 
Смежные вопросы