web-dev-qa-db-ja.com

LINQのDistinct()の等値比較子にデリゲートを使用します

次のように、独自のカスタム比較を使用するLINQ Distinct()ステートメントがあります。

class MyComparer<T> : IEqualityComparer<T> where T : MyType
{
    public bool Equals(T x, T y)
    {
        return x.Id.Equals(y.Id);
    }

    public int GetHashCode(T obj)
    {
        return obj.Id.GetHashCode();
    }
}

...

var distincts = bundle.GetAllThings.Distinct(new MyComparer<MySubType>());

これはすべて素晴らしく、ダンディで、私が望むように機能します。好奇心から、独自のComparerを定義する必要がありますか、それをデリゲートに置き換えることができますか?私はこのようなことをすることができるはずだと思った:

var distincts = bundle.GetAllThings.Distinct((a,b) => a.Id == b.Id);

しかし、これはコンパイルされません。きちんとしたトリックはありますか?

45
Aidan

Distinctは、IEqualityComparerを2番目の引数として受け取るため、IEqualityComparerが必要です。ただし、デリゲートを取る汎用的なものを作成するのはそれほど難しくありません。もちろん、これはおそらく、他の回答の1つで提案されているMoreLINQなど、いくつかの場所で既に実装されています。

次のように実装できます。

public static class Compare
{
    public static IEnumerable<T> DistinctBy<T, TIdentity>(this IEnumerable<T> source, Func<T, TIdentity> identitySelector)
    {
        return source.Distinct(Compare.By(identitySelector));
    }

    public static IEqualityComparer<TSource> By<TSource, TIdentity>(Func<TSource, TIdentity> identitySelector)
    {
        return new DelegateComparer<TSource, TIdentity>(identitySelector);
    }

    private class DelegateComparer<T, TIdentity> : IEqualityComparer<T>
    {
        private readonly Func<T, TIdentity> identitySelector;

        public DelegateComparer(Func<T, TIdentity> identitySelector)
        {
            this.identitySelector = identitySelector;
        }

        public bool Equals(T x, T y)
        {
            return Equals(identitySelector(x), identitySelector(y));
        }

        public int GetHashCode(T obj)
        {
            return identitySelector(obj).GetHashCode();
        }
    }
}

構文は次のとおりです。

source.DistinctBy(a => a.Id);

または、この方法がより明確であると感じる場合:

source.Distinct(Compare.By(a => a.Id));
104
driis

Distinctがそのようなオーバーロードを思い付かないのは残念なことなので、あなたが持っているのは良い選択肢です。

MoreLinq では、 DistinctBy 演算子を使用できます。

var distincts = bundle.GetAllThings.DistinctBy(a => a.Id); 

また、適切なデリゲートを here のようなIEqualityComparer<T>実装に変換できる汎用のProjectionEqualityComparerの作成を検討することもできます。

11
Ani

この link は、指定した方法でDistinctを使用できるように拡張メソッドを作成する方法を示しています。 2つのDistinct拡張メソッドと1つのIEqualityComparerを記述する必要があります。

サイトからのコードは次のとおりです。

public static class Extensions
    {
        public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source, Func<T, T, bool> comparer)
        {           
            return source.Distinct(new DelegateComparer<T>(comparer));
        }

        public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source, Func<T, T, bool> comparer, Func<T,int> hashMethod)
        {
            return source.Distinct(new DelegateComparer<T>(comparer,hashMethod));
        }
    }

    public class DelegateComparer<T> : IEqualityComparer<T>
    {
        private Func<T, T, bool> _equals;
        private Func<T,int> _getHashCode;

        public DelegateComparer(Func<T, T, bool> equals)
        {
            this._equals = equals;
        }

        public DelegateComparer(Func<T, T, bool> equals, Func<T,int> getHashCode)
        {
            this._equals = equals;
            this._getHashCode = getHashCode;
        }

        public bool Equals(T a, T b)
        {
            return _equals(a, b);
        }

        public int GetHashCode(T a)
        {
            if (_getHashCode != null)       
                return _getHashCode(a);       
            else
                return a.GetHashCode();
        }
    }
2
Ahmed Subhani