Почему нет метода Linq для возврата отдельных значений предикатом?
Я хочу получить отдельные значения в списке, но не стандартным сравнением равенства.
Я хочу сделать что-то вроде этого:
return myList.Distinct( (x, y) => x.Url == y.Url );
Я не могу, в Linq не будет метода расширения, который будет делать это - только один, который принимает IEqualityComparer
.
Я могу взломать его с помощью этого:
return myList.GroupBy( x => x.Url ).Select( g => g.First() );
Но это кажется беспорядочным. Это также не совсем то же самое - я могу использовать его только здесь, потому что у меня есть один ключ.
Я также могу добавить свой собственный:
public static IEnumerable<T> Distinct<T>(
this IEnumerable<T> input, Func<T,T,bool> compare )
{
//write my own here
}
Но это скорее похоже на то, что должно быть там, в первую очередь.
Кто-нибудь знает, почему этого метода нет?
Я что-то пропустил?
Ответы
Ответ 1
Это раздражает, конечно. Это также часть моего проекта "MoreLINQ", на который я должен обратить внимание некоторое время:) Существует множество других операций, которые имеют смысл, когда они действуют на проекцию, но возвращают оригинал - MaxBy и MinBy spring.
Как вы говорите, легко писать - хотя я предпочитаю имя "DistinctBy" для соответствия OrderBy и т.д. Здесь моя реализация, если вам интересно:
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector)
{
return source.DistinctBy(keySelector,
EqualityComparer<TKey>.Default);
}
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (keySelector == null)
{
throw new ArgumentNullException("keySelector");
}
if (comparer == null)
{
throw new ArgumentNullException("comparer");
}
return DistinctByImpl(source, keySelector, comparer);
}
private static IEnumerable<TSource> DistinctByImpl<TSource, TKey>
(IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer)
{
HashSet<TKey> knownKeys = new HashSet<TKey>(comparer);
foreach (TSource element in source)
{
if (knownKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
Ответ 2
Но это кажется грязным.
Это не грязно, это правильно.
- Если вам нужны
Distinct
программисты от FirstName и есть четыре Эми, какой из них вам нужен?
- Если вы
Group
программисты By FirstName и выбрали First
, тогда ясно, что вы хотите сделать в случае с четырьмя Эми.
Я могу использовать его только здесь, потому что у меня есть один ключ.
Вы можете сделать несколько ключей "отличным" с тем же шаблоном:
return myList
.GroupBy( x => new { x.Url, x.Age } )
.Select( g => g.First() );
Ответ 3
Джон, ваше решение довольно хорошо. Однако одно незначительное изменение. Я не думаю, что нам нужен EqualityComparer.Default. Вот мое решение (конечно, отправной точкой было решение Джона Скита)
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector)
{
//TODO All arg checks
HashSet<TKey> keys = new HashSet<TKey>();
foreach (T item in source)
{
TKey key = keySelector(item);
if (!keys.Contains(key))
{
keys.Add(key);
yield return item;
}
}
}
Ответ 4
Используя AmyB answer, я написал небольшой метод расширения DistinctBy
, позволяющий передавать предикат:
/// <summary>
/// Distinct method that accepts a perdicate
/// </summary>
/// <typeparam name="TSource">The type of the t source.</typeparam>
/// <typeparam name="TKey">The type of the t key.</typeparam>
/// <param name="source">The source.</param>
/// <param name="predicate">The predicate.</param>
/// <returns>IEnumerable<TSource>.</returns>
/// <exception cref="System.ArgumentNullException">source</exception>
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source,
Func<TSource, TKey> predicate)
{
if (source == null)
throw new ArgumentNullException("source");
return source
.GroupBy(predicate)
.Select(x => x.First());
}
Теперь вы можете передать предикат для группировки списка:
var distinct = myList.DistinctBy(x => x.Id);
Или сгруппировать по нескольким свойствам:
var distinct = myList.DistinctBy(x => new { x.Id, x.Title });