web-dev-qa-db-ja.com

匿名型のLINQ Select Distinct

オブジェクトのコレクションがあります。正確なタイプは重要ではありません。それから、特定のプロパティのペアの一意のペアをすべて抽出します。

myObjectCollection.Select(item=>new
                                {
                                     Alpha = item.propOne,
                                     Bravo = item.propTwo
                                }
                 ).Distinct();

私の質問は次のとおりです:この場合、Distinctはデフォルトのオブジェクト等しいを使用します(各オブジェクトは新しいため、私にとっては役に立たないでしょう)または異なる等しいを行うように指示することができます=>等しいインスタンス)?これができない場合、その結果を達成する方法はありますか?

144
GWLlosa

K. Scott Allenの素晴らしい投稿を読んでください。

およびすべての平等...匿名型

短い答え(そして私は引用):

C#コンパイラは、匿名型のEqualsおよびGetHashCodeをオーバーライドします。 2つのオーバーライドされたメソッドの実装では、型のすべてのパブリックプロパティを使用して、オブジェクトのハッシュコードを計算し、等しいかどうかをテストします。同じ匿名タイプの2つのオブジェクトのプロパティがすべて同じ値である場合、オブジェクトは等しくなります。

したがって、匿名型を返すクエリでDistinct()メソッドを使用することは完全に安全です。

178
Matt Hamilton
public class DelegateComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, bool> _equals;
    private Func<T, int> _hashCode;
    public DelegateComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        _equals= equals;
        _hashCode = hashCode;
    }
    public bool Equals(T x, T y)
    {
        return _equals(x, y);
    }

    public int GetHashCode(T obj)
    {
        if(_hashCode!=null)
            return _hashCode(obj);
        return obj.GetHashCode();
    }       
}

public static class Extensions
{
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, 
        Func<T, T, bool> equals, Func<T,int> hashCode)
    {
        return items.Distinct(new DelegateComparer<T>(equals, hashCode));    
    }
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items,
        Func<T, T, bool> equals)
    {
        return items.Distinct(new DelegateComparer<T>(equals,null));
    }
}

var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
            .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();

前のめちゃくちゃなフォーマットでごめんなさい

14
Nabin Nepal

C#では機能するがVBでは機能しないことは興味深い

26文字を返します。

var MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ";
MyBet.ToCharArray()
.Select(x => new {lower = x.ToString().ToLower(), upper = x.ToString().ToUpper()})
.Distinct()
.Dump();

52を返します...

Dim MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"
MyBet.ToCharArray() _
.Select(Function(x) New With {.lower = x.ToString.ToLower(), .upper = x.ToString.ToUpper()}) _
.Distinct() _
.Dump()
5
GeorgeBarker

私は少しテストを実行しましたが、プロパティが値型であれば、うまくいくようです。それらが値型でない場合、その型は、それが機能するために独自のEqualsおよびGetHashCode実装を提供する必要があります。文字列は機能すると思います。

4
tvanfosson

ラムダ式を取る独自のDistinct Extensionメソッドを作成できます。ここに例があります

IEqualityComparerインターフェイスから派生するクラスを作成します

public class DelegateComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, bool> _equals;
    private Func<T, int> _hashCode;
    public DelegateComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        _equals= equals;
        _hashCode = hashCode;
    }
    public bool Equals(T x, T y)
    {
        return _equals(x, y);
    }

    public int GetHashCode(T obj)
    {
        if(_hashCode!=null)
            return _hashCode(obj);
        return obj.GetHashCode();
    }       
}

次に、Distinct Extensionメソッドを作成します

public static class Extensions
{
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, 
        Func<T, T, bool> equals, Func<T,int> hashCode)
    {
        return items.Distinct(new DelegateComparer<T>(equals, hashCode));    
    }
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items,
        Func<T, T, bool> equals)
    {
        return items.Distinct(new DelegateComparer<T>(equals,null));
    }
}

このメソッドを使用して、個別のアイテムを見つけることができます

var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
            .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();
2
Nabin Nepal

ちょっとそこに同じ問題があり、解決策を見つけました。 IEquatableインターフェイスを実装するか、単に(Equals&GetHashCode)メソッドをオーバーライドする必要があります。しかし、これはトリックではなく、GetHashCodeメソッドに含まれるトリックです。クラスのオブジェクトのハッシュコードを返すべきではありませんが、そのように比較したいプロパティのハッシュを返すべきです。

public override bool Equals(object obj)
    {
        Person p = obj as Person;
        if ( obj == null )
            return false;
        if ( object.ReferenceEquals( p , this ) )
            return true;
        if ( p.Age == this.Age && p.Name == this.Name && p.IsEgyptian == this.IsEgyptian )
            return true;
        return false;
        //return base.Equals( obj );
    }
    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }

ご覧のとおり、personというクラスがあり、3つのプロパティ(Name、Age、IsEgyptian "Because I am")を取得しました。GetHashCodeでは、Personオブジェクトではなく、Nameプロパティのハッシュを返しました。

試してみると、ISAで機能します。ありがとう、Modather Sadik

0
Modather Sadik

VB.NETで機能させるには、次のように、匿名型のすべてのプロパティの前にKeyキーワードを指定する必要があります。

myObjectCollection.Select(Function(item) New With
{
    Key .Alpha = item.propOne,
    Key .Bravo = item.propTwo
}).Distinct()

私はこれに苦労していました。VB.NETはこのタイプの機能をサポートしていなかったと思っていましたが、実際にはサポートしています。

0
Alisson

AlphaBravoの両方が共通のクラスから継承する場合、IEquatable<T>を実装することにより、親クラスでの等価性チェックを指示できます。

例えば:

public class CommonClass : IEquatable<CommonClass>
{
    // needed for Distinct()
    public override int GetHashCode() 
    {
        return base.GetHashCode();
    }

    public bool Equals(CommonClass other)
    {
        if (other == null) return false;
        return [equality test];
    }
}
0
ern