Информация об изменениях

Сообщение Re: Distinct и Co - грабли в поле от 26.03.2015 18:06

Изменено 26.03.2015 18:09 _temp

Code fixed

Здравствуйте, btn1, Вы писали:

B>Предыстория вопроса: Есть у нас IEnumerable<T>.Distinct; Метод, конечно, хороший, но был спроектирован на "отъ***ись" (кол бы потесать на башке .NET team leader). Почему? Да потому что при использовании сего метода, кастомный "сравниватель на эквивалентность" не может быть лямбдой! (хотя казалось бы, зачем вообще вводить лямбды, если вы их толком не даёте использовать?!!) Поясню примером:

B>var lst = new List<MyClass>(); // почти в каждой программе есть списки "непойми чего", элементы которых НЕ МОГУТ сравниваться "в лоб"
B>var uniq = lst.Distinct((a, b) => { return a.SomeField == b.SomeField; }); // это КАК НАДО было проектировать Distinct, чтобы он был реально полезен


Передо мной такие задачи встают регулярно, я делаю так:
private static readonly EqualityComparer<MyClass> MyClassBySomeFieldEqualityComparer = new ComparerBuilder<MyClass>()
  .AddEquality(item => item.SomeField)
  .ToEqualityComparer();
…
var items = new List<MyClass>();
var unique = items.Distinct(MyClassBySomeFieldEqualityComparer);

Если нужно сравнивать несколько полей из MyClass, и/или надо не только сравнивать на равенство но и на больше-меньше/сортировать, то так:
private static readonly ComparerBuilder<MyClass> MyClassCustomComparerBuilder = new ComparerBuilder<MyClass>()
  .Add(item => item.SomeField)
  // Если надо без учёта регистра
  .Add(item => item.SomeTextField, StringComparer.OrdinalIgnoreCase);
  // Так же, можно удобно и просто справнивать не только "линейные" поля, 
  // но и произвольные колекции/словари/мульти-словари и т.п.; с учётом порядка элементов или без него

private static readonly EqualityComparer<MyClass> MyClassCustomEqualityComparer = MyClassAddEqualityComparerBuilder.ToEqualityComparer();
private static readonly Comparer<MyClass> MyClassCustomComparer = MyClassAddEqualityComparerBuilder.ToComparer();

…
var items = new List<MyClass>();
items.Sort(MyClassCustomComparer);
var unique = items.Distinct(MyClassCustomEqualityComparer);

Добавление нового поля таким образом ни разу не является проблемой и дублирование кода исключено. О чёрной магии хеш-кодов можно забыть как страшный сон и и не знать о ней ещё сто лет.

Класс ComparerBuilder<> есть тут в Исходниках и небольшой докой
Автор: _temp
Дата: 22.03.15
, более свежий на кодеплексе. А на гитхабе более удобная версия с возможностью отладки и перехвата в пользовательском коде операций сравнения и другими улучшениями.
Re: Distinct и Co - грабли в поле
Здравствуйте, btn1, Вы писали:

B>Предыстория вопроса: Есть у нас IEnumerable<T>.Distinct; Метод, конечно, хороший, но был спроектирован на "отъ***ись" (кол бы потесать на башке .NET team leader). Почему? Да потому что при использовании сего метода, кастомный "сравниватель на эквивалентность" не может быть лямбдой! (хотя казалось бы, зачем вообще вводить лямбды, если вы их толком не даёте использовать?!!) Поясню примером:

B>var lst = new List<MyClass>(); // почти в каждой программе есть списки "непойми чего", элементы которых НЕ МОГУТ сравниваться "в лоб"
B>var uniq = lst.Distinct((a, b) => { return a.SomeField == b.SomeField; }); // это КАК НАДО было проектировать Distinct, чтобы он был реально полезен


Передо мной такие задачи встают регулярно, я делаю так:
private static readonly EqualityComparer<MyClass> MyClassBySomeFieldEqualityComparer = new ComparerBuilder<MyClass>()
  .AddEquality(item => item.SomeField)
  .ToEqualityComparer();
…
var items = new List<MyClass>();
var unique = items.Distinct(MyClassBySomeFieldEqualityComparer);

Если нужно сравнивать несколько полей из MyClass, и/или надо не только сравнивать на равенство но и на больше-меньше/сортировать, то так:
private static readonly ComparerBuilder<MyClass> MyClassCustomComparerBuilder = new ComparerBuilder<MyClass>()
  .Add(item => item.SomeField)
  // Если надо без учёта регистра
  .Add(item => item.SomeTextField, StringComparer.OrdinalIgnoreCase);
  // Так же, можно удобно и просто справнивать не только "линейные" поля, 
  // но и произвольные колекции/словари/мульти-словари и т.п.; с учётом порядка элементов или без него

private static readonly EqualityComparer<MyClass> MyClassCustomEqualityComparer = MyClassCustomComparerBuilder.ToEqualityComparer();
private static readonly Comparer<MyClass> MyClassCustomComparer = MyClassCustomComparerBuilder.ToComparer();

…
var items = new List<MyClass>();
items.Sort(MyClassCustomComparer);
var unique = items.Distinct(MyClassCustomEqualityComparer);

Добавление нового поля таким образом ни разу не является проблемой и дублирование кода исключено. О чёрной магии хеш-кодов можно забыть как страшный сон и и не знать о ней ещё сто лет.

Класс ComparerBuilder<> есть тут в Исходниках и небольшой докой
Автор: _temp
Дата: 22.03.15
, более свежий на кодеплексе. А на гитхабе более удобная версия с возможностью отладки и перехвата в пользовательском коде операций сравнения и другими улучшениями.