web-dev-qa-db-ja.com

述語によって個別の値を返すLinqメソッドがないのはなぜですか?

リスト内の個別の値を取得したいのですが、標準の等式比較では取得しません。

私がやりたいのは次のようなものです。

return myList.Distinct( (x, y) => x.Url == y.Url );

できません。Linqにはこれを実行する拡張メソッドはありません。IEqualityComparerを使用するメソッドだけです。

私はこれでそれをハックすることができます:

return myList.GroupBy( x => x.Url ).Select( g => g.First() );

しかし、それは厄介なようです。また、まったく同じことはしません。キーが1つしかないため、ここでしか使用できません。

自分で追加することもできます。

public static IEnumerable<T> Distinct<T>( 
    this IEnumerable<T> input, Func<T,T,bool> compare )
{
    //write my own here
}

しかし、それはそもそもそこにあるべき何かを書くように思えます。

この方法がない理由を誰かが知っていますか?

私は何かが足りないのですか?

49
Keith

確かに迷惑です。これは私の「MoreLINQ」プロジェクトの一部でもあり、ある時点で注意を払う必要があります:)プロジェクションを操作するときに意味のある操作は他にもたくさんありますが、元のMaxByとMinByが思い浮かびます。

あなたが言うように、書くのは簡単です-私はOrderByなどと一致するために「DistinctBy」という名前を好みますが。興味があればこれが私の実装です:

    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;
            }
        }
    }
52
Jon Skeet

しかし、それは厄介なようです。

それは厄介ではありません、それは正しいです。

  • FirstNameのDistinctプログラマーが必要で、Davidが4人いる場合、どれが必要ですか?
  • FirstNameでGroupプログラマーを選び、Firstを採用した場合、4人のDavidの場合に何をしたいかは明らかです。

キーが1つしかないので、ここでしか使用できません。

同じパターンで複数のキーを「区別」することができます。

return myList
  .GroupBy( x => new { x.Url, x.Age } )
  .Select( g => g.First() );
34
Amy B

ジョン、あなたの解決策はかなり良いです。ただし、1つの小さな変更。そこにEqualityComparer.Defaultは必要ないと思います。これが私の解決策です(もちろん、出発点はJon Skeetの解決策でした)

    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;
            }
        }
    }
3
SVC

@ DavidB 's 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&lt;TSource&gt;.</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 });
1
Cerbrus