LINQ Wybierz odrębne z typami anonimowymi

150

Mam więc kolekcję przedmiotów. Dokładny typ nie jest ważny. Chcę z niego wyodrębnić wszystkie unikalne pary pary określonych właściwości, w ten sposób:

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

Więc moje pytanie brzmi: Will Distinct w tym przypadku użyje domyślnego obiektu równa się (co będzie dla mnie bezużyteczne, ponieważ każdy obiekt jest nowy) lub czy można mu powiedzieć, aby zrobił różne równa się (w tym przypadku równe wartości Alpha i Bravo => równe instancje)? Czy jest jakiś sposób na osiągnięcie tego wyniku, jeśli to nie wystarczy?

GWLlosa
źródło
Czy to LINQ-to-Objects czy LINQ-to-SQL? Jeśli są to tylko przedmioty, prawdopodobnie nie masz szczęścia. Jednakże, jeśli L2S, to może działać, ponieważ DISTINCT zostałby przekazany do instrukcji SQL.
James Curran

Odpowiedzi:

188

Przeczytaj doskonały post K. Scotta Allena tutaj:

I równość dla wszystkich ... Anonimowe typy

Krótka odpowiedź (cytuję):

Okazuje się, że kompilator C # zastępuje Equals i GetHashCode dla typów anonimowych. Implementacja dwóch zastąpionych metod używa wszystkich właściwości publicznych typu do obliczenia kodu skrótu obiektu i przetestowania pod kątem równości. Jeśli dwa obiekty tego samego typu anonimowego mają wszystkie te same wartości swoich właściwości - obiekty są równe.

Dlatego użycie metody Distinct () w zapytaniu zwracającym typy anonimowe jest całkowicie bezpieczne.

Matt Hamilton
źródło
2
Myślę, że jest to prawdą tylko wtedy, gdy same właściwości są typami wartości lub implementują równość wartości - zobacz moją odpowiedź.
tvanfosson
Tak, ponieważ używa GetHashCode na każdej właściwości, działałoby tylko wtedy, gdyby każda właściwość miała swoją własną unikalną implementację tego. Myślę, że większość przypadków użycia obejmowałaby tylko proste typy jako właściwości, więc jest to ogólnie bezpieczne.
Matt Hamilton,
4
To kończy się oznaczeniem, że równość dwóch anonimowych typów zależy od równości członków, co jest dla mnie w porządku, ponieważ członkowie są zdefiniowani w jakimś miejscu, do którego mogę się dostać i nadpisują równość, jeśli muszę. Po prostu nie chciałem tworzyć klasy tylko po to, aby nadpisać równa się.
GWLlosa
3
Warto zwrócić się do MS o wprowadzenie składni „klucza” do C #, którą posiada VB (gdzie można określić pewne właściwości typu anonimowego jako „klucz podstawowy” - zobacz post na blogu, do którego utworzyłem łącze).
Matt Hamilton,
1
Bardzo ciekawy artykuł. Dzięki!
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();

Przepraszam za pomieszane formatowanie wcześniej


źródło
Te rozszerzenia nie obsługują typu objecti object. Jeśli oba objectstring, nadal zwraca zduplikowane wiersze. Wypróbuj FirstNameis typeof objecti przypisz stringtam to samo .
CallMeLaNN
To świetna odpowiedź dla obiektów wpisanych na maszynie, ale nie jest potrzebna dla typów anonimowych.
crokusek
5

Ciekawe, że działa w C #, ale nie w VB

Zwraca 26 liter:

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

Zwroty 52 ...

Dim MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"
MyBet.ToCharArray() _
.Select(Function(x) New With {.lower = x.ToString.ToLower(), .upper = x.ToString.ToUpper()}) _
.Distinct() _
.Dump()
GeorgeBarker
źródło
11
Jeśli dodasz Keysłowo kluczowe do typu anonimowego, .Distinct()będzie działać zgodnie z przeznaczeniem (np New With { Key .lower = x.ToString.ToLower(), Key .upper = x.ToString.ToUpper()}.).
Cᴏʀʏ
3
Cory ma rację. Prawidłowe tłumaczenie kodu C # new {A = b}to New {Key .A = b}. Właściwości niebędące kluczami w anonimowych klasach języka VB są modyfikowalne, dlatego są porównywane przez odniesienie. W języku C # wszystkie właściwości klas anonimowych są niezmienne.
Heinzi,
4

Przeprowadziłem mały test i stwierdziłem, że jeśli właściwości są typami wartości, wydaje się, że działa dobrze. Jeśli nie są to typy wartości, typ musi mieć własne implementacje Equals i GetHashCode, aby działał. Myślę, że struny będą działać.

tvanfosson
źródło
2

Możesz stworzyć własną metodę Distinct Extension, która przyjmuje wyrażenie lambda. Oto przykład

Utwórz klasę, która pochodzi od interfejsu 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();
    }       
}

Następnie utwórz metodę 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));
    }
}

i możesz użyć tej metody, aby znaleźć różne elementy

var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
            .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();
Buildstarted
źródło
Te rozszerzenia nie obsługują typu objecti object. Jeśli oba objectstring, nadal zwraca zduplikowane wiersze. Wypróbuj FirstNameis typeof objecti przypisz stringtam to samo .
CallMeLaNN
0

Jeśli Alphai Bravooboje dziedziczą ze wspólnej klasy, będzie można podyktować kontrolę równości w klasie nadrzędnej, implementując IEquatable<T>.

Na przykład:

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];
    }
}
ern
źródło
więc jeśli używasz jako właściwości swoich typów anonimowych klasy, która implementuje IEquatable <T>, równa się nazywa zamiast domyślnego (sprawdź zachowanie wszystkich właściwości publicznych za pośrednictwem refleksji?)
D_Guidi
0

Hej, mam ten sam problem i znalazłem rozwiązanie. Musisz zaimplementować interfejs IEquatable lub po prostu zastąpić metody (Equals & GetHashCode). Ale to nie jest sztuczka, sztuczka pojawiająca się w metodzie GetHashCode. Nie powinieneś zwracać skrótu obiektu swojej klasy, ale powinieneś zwrócić hash właściwości, którą chcesz porównać.

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

Jak widzisz, otrzymałem klasę o nazwie osoba, która ma 3 właściwości (Imię, Wiek, IsEgyptian „Ponieważ jestem”). W GetHashCode zwróciłem hash właściwości Name, a nie obiektu Person.

Wypróbuj i zadziała ISA. Dziękuję, Modather Sadik

Modather Sadik
źródło
1
GetHashCode powinien używać wszystkich tych samych pól i właściwości, które są używane w porównaniu do równości, a nie tylko jednej z nich. tj.public override int GetHashCode() { return this.Name.GetHashCode() ^ this.Age.GetHashCode() ^ this.IsEgyptian.GetHashCode(); }
JG w SD
Aby uzyskać informacje na temat generowania dobrego algorytmu mieszającego: stackoverflow.com/questions/263400/…
JG w SD,
0

Aby to działało w VB.NET, musisz podać Keysłowo kluczowe przed każdą właściwością w typie anonimowym, tak jak poniżej:

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

Zmagałem się z tym, myślałem, że VB.NET nie obsługuje tego typu funkcji, ale w rzeczywistości tak.

Alissona
źródło