匿名型のLINQ Select Distinct


150

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

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

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


これはLINQ-to-Objectsですか、それともLINQ-to-SQLですか?オブジェクトだけの場合は、おそらく運が悪いでしょう。ただし、L2Sの場合、DISTINCTがSQLステートメントに渡されるため、機能する可能性があります。
ジェームズカラン

回答:


188

K.スコットアレンの優れた投稿を読んでください。

そして、すべての平等...匿名型

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

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

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


2
これは、プロパティ自体が値型であるか、値の等価性を実装している場合にのみ当てはまると思います。私の答えを参照してください。
tvanfosson 2009

はい、各プロパティでGetHashCodeを使用しているため、各プロパティに独自の実装がある場合にのみ機能します。ほとんどのユースケースでは、プロパティとして単純な型のみが関係するため、一般的に安全だと思います。
マットハミルトン

4
これは、2つの匿名型の等価性がメンバーの等価性に依存することを意味します。これは、メンバーがどこかで定義され、必要に応じて等価性をオーバーライドできるためです。等しいをオーバーライドするためだけに、このクラスを作成する必要はありませんでした。
GWLlosa 2009

3
VBが持っているC#に「キー」構文を導入することをMSに請願する価値があるかもしれません(「主キー」になるように匿名型の特定のプロパティを指定できます-私がリンクしているブログ投稿を参照してください)。
Matt Hamilton、

1
非常に興味深い記事。ありがとう!
Alexander Prokofyev、

14
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();

以前にめちゃくちゃになったフォーマットでごめんなさい


この拡張機能ではタイプobjectとを処理できませんobject。場合の両方がobjectありstring、それはまだ重複行を返します。FirstNameis typeof objectを試して、stringそこに同じものを割り当てます。
CallMeLaNN

これは、型指定されたオブジェクトには最適な答えですが、匿名型には必要ありません。
crokusek 2017年

5

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()

11
Key匿名タイプにキーワードを追加すると、.Distinct()意図したとおりに機能します(例:)New With { Key .lower = x.ToString.ToLower(), Key .upper = x.ToString.ToUpper()}
Cᴏʀʏ

3
コーリーは正しいです。C#コードの正しい変換new {A = b}New {Key .A = b}です。匿名VBクラスの非キープロパティは変更可能です。そのため、それらは参照によって比較されます。C#では、匿名クラスのすべてのプロパティは不変です。
ハインツィ

4

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


2

ラムダ式を取る独自の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();

この拡張機能ではタイプobjectとを処理できませんobject。場合の両方がobjectありstring、それはまだ重複行を返します。FirstNameis typeof objectを試して、stringそこに同じものを割り当てます。
CallMeLaNN

0

両方が共通のクラスを継承している場合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];
    }
}

したがって、IEquatable <T>を実装する匿名型クラスのプロパティとして使用する場合、デフォルトの動作の代わりにEqualsが呼び出されます(リフレクションによるすべてのパブリックプロパティのチェック?)
D_Guidi

0

こんにちは、同じ問題が発生し、解決策が見つかりました。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


1
GetHashCodeは、比較で使用されるのと同じフィールドとプロパティのすべてを使用する必要があります。iepublic override int GetHashCode() { return this.Name.GetHashCode() ^ this.Age.GetHashCode() ^ this.IsEgyptian.GetHashCode(); }
SDのJG、

:良いハッシュアルゴリズムの生成の詳細についてはstackoverflow.com/questions/263400/...
SDでJGを

0

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.