Некоторые функции Linq.Enumerable принимают расширение IEqualityComparer<T>
. Есть ли удобный класс-оболочка, который адаптирует delegate(T,T)=>bool
для реализации IEqualityComparer<T>
? Его достаточно легко написать (если вы игнорируете проблемы с определением правильного хэш-кода), но я хотел бы знать, есть ли готовое решение.
В частности, я хочу выполнять операции set с Dictionary
s, используя только ключи для определения членства (при сохранении значений в соответствии с разными правилами).
IEqualityComparer<T>
что упускаетсяGetHashCode
, просто ломается.О важности
GetHashCode
Другие уже прокомментировали тот факт, что любая настраиваемая
IEqualityComparer<T>
реализация действительно должна включатьGetHashCode
метод ; но никто не удосужился подробно объяснить почему .Вот почему. В вашем вопросе конкретно упоминаются методы расширения LINQ; почти все они полагаются на хэш-коды для правильной работы, потому что они используют хэш-таблицы внутри для повышения эффективности.
Взять
Distinct
, к примеру. Рассмотрим последствия этого метода расширения, если бы все, что он использовал, былоEquals
методом. Как вы определяете, был ли элемент уже отсканирован в последовательности, если вы только сканировалиEquals
? Вы перебираете всю коллекцию значений, которые уже просмотрели, и проверяете соответствие. Это приведет кDistinct
использованию алгоритма O (N 2 ) в худшем случае вместо алгоритма O (N)!К счастью, это не так.
Distinct
не просто использоватьEquals
; он также используетGetHashCode
. На самом деле, он абсолютно не работает должным образом без соответствующегоIEqualityComparer<T>
источника питанияGetHashCode
. Ниже приведен надуманный пример, иллюстрирующий это.Скажем, у меня есть следующий тип:
Теперь предположим, что у меня есть,
List<Value>
и я хочу найти все элементы с разными именами. Это идеальный вариантDistinct
использования настраиваемого компаратора равенства. Итак, давайте использоватьComparer<T>
класс из ответа Аку :Теперь, если у нас есть группа
Value
элементов с одним и тем жеName
свойством, все они должны свернуться в одно значение, возвращаемоеDistinct
, верно? Посмотрим...Вывод:
Хм, это не сработало, не так ли?
О чем
GroupBy
? Попробуем это:Вывод:
Опять же: не сработало.
Если вы думаете об этом, было бы разумно
Distinct
использоватьHashSet<T>
(или эквивалент) внутренне, аGroupBy
также использовать что-то вродеDictionary<TKey, List<T>>
внутреннего. Может ли это объяснить, почему эти методы не работают? Попробуем это:Вывод:
Да ... начинает иметь смысл?
Надеюсь, из этих примеров понятно, почему включение подходящего
GetHashCode
в любуюIEqualityComparer<T>
реализацию так важно.Оригинальный ответ
Расширяя ответ orip :
Здесь можно сделать несколько улучшений.
Func<T, TKey>
вместоFunc<T, object>
; это предотвратит упаковку ключей типа значения в само фактическое значениеkeyExtractor
.where TKey : IEquatable<TKey>
ограничение; это предотвратит упаковку вEquals
вызове (object.Equals
принимаетobject
параметр; вам нужнаIEquatable<TKey>
реализация, чтобы приниматьTKey
параметр без его упаковки). Ясно, что это может представлять слишком серьезное ограничение, поэтому вы можете создать базовый класс без ограничения и производный класс с ним.Вот как может выглядеть полученный код:
источник
StrictKeyEqualityComparer.Equals
метод выглядит таким же, какKeyEqualityComparer.Equals
. Есть лиTKey : IEquatable<TKey>
ограничение делает по-TKey.Equals
разному работает?TKey
может быть любой произвольный тип, компилятор будет использовать виртуальный метод,Object.Equals
который потребует упаковки параметров типа значения, напримерint
. Однако в последнем случае, посколькуTKey
реализация ограниченаIEquatable<TKey>
,TKey.Equals
будет использоваться метод, который не требует упаковки.StringKeyEqualityComparer<T, TKey>
тоже.Если вы хотите настроить проверку равенства, в 99% случаев вы заинтересованы в определении ключей для сравнения, а не в самом сравнении.
Это может быть элегантное решение (концепция из метода сортировки списков Python ).
Использование:
KeyEqualityComparer
Класс:источник
Func<T, int>
чтобы предоставить хэш-код дляT
значения (как было предложено, например, в ответе Рубена ). В противном случаеIEqualityComparer<T>
реализация, с которой вы остались, будет совершенно нарушена, особенно в отношении ее полезности в методах расширения LINQ. См. Мой ответ, почему это так.Боюсь, из коробки такой обертки нет. Однако создать его несложно:
источник
private readonly Func<T, int> _hashCodeResolver
который также должен быть передан в конструктор и использован вGetHashCode(...)
методе.obj.ToString().ToLower().GetHashCode()
вместоobj.GetHashCode()
?IEqualityComparer<T>
неизменно используют хеширование за кулисами (например, LINQ GroupBy, Distinct, Except, Join и т. Д.), И контракт MS относительно хеширования нарушается в этой реализации. Вот отрывок из документации MS: «Реализации необходимы, чтобы гарантировать, что если метод Equals возвращает true для двух объектов x и y, то значение, возвращаемое методом GetHashCode для x, должно быть равно значению, возвращаемому для y». См .: msdn.microsoft.com/en-us/library/ms132155То же, что и ответ Дэна Тао, но с некоторыми улучшениями:
Используется
EqualityComparer<>.Default
для фактического сравнения, чтобы избежать упаковки дляstruct
реализованных типов значенийIEquatable<>
.С момента
EqualityComparer<>.Default
использования не взрываетсяnull.Equals(something)
.Предоставлена статическая оболочка, вокруг
IEqualityComparer<>
которой будет статический метод для создания экземпляра компаратора - упрощает вызов. сравнитьс участием
Добавлена перегрузка для указания
IEqualityComparer<>
ключа.Класс:
вы можете использовать это так:
Person - это простой класс:
источник
С расширениями: -
источник
Орип ответ великолепен.
Вот небольшой способ расширения, чтобы сделать его еще проще:
источник
Я отвечу на свой вопрос. Чтобы обрабатывать словари как наборы, кажется, самый простой метод - применить операции с наборами к dict.Keys, а затем преобразовать их обратно в словари с помощью Enumerable.ToDictionary (...).
источник
Реализация в (немецкий текст) Реализация IEqualityCompare с лямбда-выражением заботится о нулевых значениях и использует методы расширения для создания IEqualityComparer.
Чтобы создать IEqualityComparer в объединении Linq, вам просто нужно написать
Компаратор:
Вам также необходимо добавить метод расширения для поддержки вывода типа
источник
Всего одна оптимизация: мы можем использовать готовый EqualityComparer для сравнения значений, а не делегировать его.
Это также сделало бы реализацию более чистой, поскольку фактическая логика сравнения теперь остается в GetHashCode () и Equals (), которые вы, возможно, уже перегрузили.
Вот код:
Не забудьте перегрузить методы GetHashCode () и Equals () на вашем объекте.
Этот пост мне помог: c # сравнить два общих значения
Sushil
источник
Орип ответ великолепен. Расширяя ответ orip:
Я думаю, что ключом решения является использование «метода расширения» для передачи «анонимного типа».
Использование:
источник
Это позволяет выбрать свойство с лямбдой следующим образом:
.Select(y => y.Article).Distinct(x => x.ArticleID);
источник
Я не знаю существующего класса, но что-то вроде:
Примечание: на самом деле я еще не скомпилировал и не запускал это, поэтому может быть опечатка или другая ошибка.
источник