LINQ Wählen Sie Distinct with Anonymous Types


150

Ich habe also eine Sammlung von Objekten. Der genaue Typ ist nicht wichtig. Daraus möchte ich alle eindeutigen Paare eines Paares bestimmter Eigenschaften extrahieren, also:

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

Meine Frage lautet also: Wird Distinct in diesem Fall das Standardobjekt gleich verwenden (was für mich nutzlos ist, da jedes Objekt neu ist) oder kann es angewiesen werden, ein anderes Gleichwert zu verwenden (in diesem Fall gleiche Werte von Alpha und Bravo) => gleiche Instanzen)? Gibt es eine Möglichkeit, dieses Ergebnis zu erzielen, wenn dies nicht der Fall ist?


Ist das LINQ-to-Objects oder LINQ-to-SQL? Wenn Sie nur Objekte haben, haben Sie wahrscheinlich kein Glück. Wenn jedoch L2S, kann dies funktionieren, da DISTINCT an die SQL-Anweisung übergeben wird.
James Curran

Antworten:


187

Lesen Sie hier den ausgezeichneten Beitrag von K. Scott Allen durch:

Und Gleichheit für alle ... Anonyme Typen

Die kurze Antwort (und ich zitiere):

Es stellt sich heraus, dass der C # -Compiler Equals und GetHashCode für anonyme Typen überschreibt. Bei der Implementierung der beiden überschriebenen Methoden werden alle öffentlichen Eigenschaften des Typs verwendet, um den Hashcode eines Objekts zu berechnen und auf Gleichheit zu testen. Wenn zwei Objekte desselben anonymen Typs dieselben Werte für ihre Eigenschaften haben, sind die Objekte gleich.

Daher ist es absolut sicher, die Distinct () -Methode für eine Abfrage zu verwenden, die anonyme Typen zurückgibt.


2
Dies ist meiner Meinung nach nur dann richtig, wenn die Eigenschaften selbst Werttypen sind oder Wertgleichheit implementieren - siehe meine Antwort.
Tvanfosson

Ja, da GetHashCode für jede Eigenschaft verwendet wird, funktioniert es nur, wenn jede Eigenschaft eine eigene Implementierung davon hat. Ich denke, die meisten Anwendungsfälle würden nur einfache Typen als Eigenschaften beinhalten, so dass es im Allgemeinen sicher ist.
Matt Hamilton

4
Es bedeutet, dass die Gleichheit von zwei der anonymen Typen von der Gleichheit der Mitglieder abhängt, was für mich in Ordnung ist, da die Mitglieder an einem Ort definiert sind, an dem ich Gleichheit erreichen und überschreiben kann, wenn ich muss. Ich wollte einfach keine Klasse dafür erstellen müssen, um Gleichheit zu überschreiben.
GWLlosa

3
Es könnte sich lohnen, bei MS eine Petition einzureichen, um die "Schlüssel" -Syntax in C # einzuführen, über die VB verfügt (wo Sie bestimmte Eigenschaften eines anonymen Typs als "Primärschlüssel" angeben können - siehe den Blog-Beitrag, auf den ich verlinkt habe).
Matt Hamilton

1
Sehr interessanter Artikel. Vielen Dank!
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();

Entschuldigen Sie die fehlerhafte Formatierung früher


Diese Erweiterungen können nicht vom Typ objectund verarbeiten object. Wenn beides der Fall objectist string, werden die doppelten Zeilen immer noch zurückgegeben. Probieren Sie den FirstNameis-Typ aus objectund weisen Sie ihn stringdort zu.
CallMeLaNN

Dies ist eine großartige Antwort für typisierte Objekte, die jedoch für anonyme Typen nicht benötigt wird.
Crokusek

5

Interessant, dass es in C # funktioniert, aber nicht in VB

Gibt die 26 Buchstaben zurück:

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

Rückgabe 52 ...

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

11
Wenn Sie das KeySchlüsselwort zum anonymen Typ hinzufügen, .Distinct()funktioniert dies wie beabsichtigt (z New With { Key .lower = x.ToString.ToLower(), Key .upper = x.ToString.ToUpper()}. B. ).
11.

3
Cory hat recht. Die korrekte Übersetzung des C # -Codes new {A = b}lautet New {Key .A = b}. Nicht-Schlüsseleigenschaften in anonymen VB-Klassen sind veränderbar, weshalb sie als Referenz verglichen werden. In C # sind alle Eigenschaften anonymer Klassen unveränderlich.
Heinzi

4

Ich habe einen kleinen Test durchgeführt und festgestellt, dass es in Ordnung ist, wenn die Eigenschaften Werttypen sind. Wenn es sich nicht um Werttypen handelt, muss der Typ seine eigenen Equals- und GetHashCode-Implementierungen bereitstellen, damit er funktioniert. Saiten, würde ich denken, würden funktionieren.


2

Sie können Ihre eigene Distinct Extension-Methode erstellen, die den Lambda-Ausdruck verwendet. Hier ist ein Beispiel

Erstellen Sie eine Klasse, die von der IEqualityComparer-Schnittstelle abgeleitet ist

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

Erstellen Sie dann Ihre Distinct Extension-Methode

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));
    }
}

Mit dieser Methode können Sie verschiedene Elemente finden

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

Diese Erweiterungen können nicht vom Typ objectund verarbeiten object. Wenn beides der Fall objectist string, werden die doppelten Zeilen immer noch zurückgegeben. Probieren Sie den FirstNameis-Typ aus objectund weisen Sie ihn stringdort zu.
CallMeLaNN

0

Wenn Alphaund Bravobeide von einer gemeinsamen Klasse erben, können Sie die Gleichheitsprüfung in der übergeordneten Klasse durch Implementierung diktieren IEquatable<T>.

Beispielsweise:

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];
    }
}

Wenn Sie also als Eigenschaften Ihrer anonymen Typklassen Klassen verwenden, die IEquatable <T> implementieren, wird Equals anstelle des Standardverhaltens aufgerufen (Überprüfung aller öffentlichen Eigenschaften durch Reflektion?)
D_Guidi

0

Hey da habe ich das gleiche Problem und ich habe eine Lösung gefunden. Sie müssen die IEquatable-Schnittstelle implementieren oder einfach die (Equals & GetHashCode) -Methoden überschreiben. Dies ist jedoch nicht der Trick, der in der GetHashCode-Methode enthalten ist. Sie sollten nicht den Hash-Code des Objekts Ihrer Klasse zurückgeben, sondern den Hash der Eigenschaft, die Sie so vergleichen möchten.

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

Wie Sie sehen, habe ich eine Klasse namens Person mit 3 Eigenschaften erhalten (Name, Alter, ägyptisch "Weil ich bin"). Im GetHashCode habe ich den Hash der Eigenschaft Name zurückgegeben, nicht das Objekt Person.

Probieren Sie es aus und es wird ISA funktionieren. Vielen Dank, Modather Sadik


1
GetHashCode sollte dieselben Felder und Eigenschaften verwenden, die im Vergleich für die Gleichheit verwendet werden, nicht nur eines davon. dhpublic override int GetHashCode() { return this.Name.GetHashCode() ^ this.Age.GetHashCode() ^ this.IsEgyptian.GetHashCode(); }
JG in SD

Informationen zum Generieren eines guten Hash-Algorithmus finden Sie unter: stackoverflow.com/questions/263400/…
JG in SD,

0

Damit es in VB.NET funktioniert, müssen Sie das KeySchlüsselwort vor jeder Eigenschaft im anonymen Typ wie folgt angeben :

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

Ich hatte damit zu kämpfen, ich dachte, VB.NET unterstützt diese Art von Funktion nicht, aber tatsächlich.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.