LINQ Pilih Perbedaan dengan Jenis Anonim

150

Jadi saya punya koleksi benda. Jenis pastinya tidak penting. Dari sini saya ingin mengekstrak semua pasangan unik dari sepasang properti tertentu, dengan demikian:

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

Jadi pertanyaan saya adalah: Apakah Perbedaan dalam hal ini menggunakan objek default sama dengan (yang akan berguna bagi saya, karena setiap objek baru) atau dapatkah dikatakan untuk melakukan yang sama dengan yang berbeda (dalam hal ini, nilai yang sama dari Alpha dan Bravo => instance yang sama)? Apakah ada cara untuk mencapai hasil itu, jika ini tidak berhasil?

GWLlosa
sumber
Apakah ini LINQ-to-Objects atau LINQ-to-SQL? Jika hanya objek, Anda mungkin kurang beruntung. Namun, jika L2S, maka mungkin berfungsi, karena DISTINCT akan diteruskan ke pernyataan SQL.
James Curran

Jawaban:

188

Bacalah tulisan bagus K. Scott Allen di sini:

Dan Kesetaraan untuk Semua ... Jenis Anonim

Jawaban singkatnya (dan saya kutip):

Ternyata kompiler C # menimpa Equals dan GetHashCode untuk tipe anonim. Implementasi dari dua metode yang diganti menggunakan semua properti publik pada tipe untuk menghitung kode hash objek dan menguji kesetaraan. Jika dua objek dengan tipe anonim yang sama memiliki semua nilai yang sama untuk propertinya - objeknya sama.

Jadi benar-benar aman untuk menggunakan metode Distinct () pada kueri yang mengembalikan tipe anonim.

Matt Hamilton
sumber
2
Ini hanya benar, saya pikir, jika properti itu sendiri adalah tipe nilai atau mengimplementasikan kesetaraan nilai - lihat jawaban saya.
tvanfosson
Ya, karena menggunakan GetHashCode pada setiap properti maka itu hanya akan berfungsi jika masing-masing properti memiliki implementasi uniknya sendiri. Saya pikir sebagian besar kasus penggunaan hanya akan melibatkan tipe sederhana sebagai properti sehingga umumnya aman.
Matt Hamilton
4
Itu berakhir berarti bahwa kesetaraan dua jenis anonim tergantung pada kesetaraan anggota, yang saya anggap baik, karena anggota ditentukan di suatu tempat yang bisa saya peroleh dan menimpa kesetaraan jika saya harus. Saya hanya tidak ingin harus membuat kelas untuk ini hanya untuk mengesampingkan persamaan.
GWLlosa
3
Mungkin layak mengajukan petisi untuk memperkenalkan sintaks "kunci" ke dalam C # yang dimiliki VB (di mana Anda dapat menentukan properti tertentu dari tipe anonim sebagai 'kunci utama' - lihat posting blog yang saya tautkan).
Matt Hamilton
1
Artikel yang sangat menarik. Terima kasih!
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();

Maaf untuk pemformatan yang kacau sebelumnya


sumber
Ekstensi ini tidak dapat menangani tipe objectdan object. Jika kedua objectadalah stringmasih mengembalikan duplikasi baris. Coba FirstNameketik isof objectdan tetapkan dengan yang sama di stringsana.
CallMeLaNN
Ini adalah jawaban yang bagus untuk objek yang diketik tetapi tidak diperlukan untuk jenis anonim.
crokusek
5

Menarik bahwa ia bekerja di C # tetapi tidak di VB

Mengembalikan 26 huruf:

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

Pengembalian 52 ...

Dim MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"
MyBet.ToCharArray() _
.Select(Function(x) New With {.lower = x.ToString.ToLower(), .upper = x.ToString.ToUpper()}) _
.Distinct() _
.Dump()
GeorgeBarker
sumber
11
Jika Anda menambahkan Keykata kunci ke jenis anonim, itu .Distinct()akan berfungsi sebagaimana dimaksud (misalnya New With { Key .lower = x.ToString.ToLower(), Key .upper = x.ToString.ToUpper()}).
Cᴏʀʏ
3
Cory benar. Terjemahan kode C # yang benar new {A = b}adalah New {Key .A = b}. Properti non-kunci dalam kelas VB anonim bisa berubah, itulah sebabnya mereka dibandingkan dengan referensi. Di C #, semua properti dari kelas anonim tidak dapat diubah.
Heinzi
4

Saya menjalankan sedikit tes dan menemukan bahwa jika properti adalah tipe nilai, sepertinya berfungsi ok. Jika mereka bukan tipe nilai, maka kebutuhan tipe itu menyediakan implementasi Equals dan GetHashCode sendiri agar bisa berfungsi. Saya pikir, string akan berhasil.

tvanfosson
sumber
2

Anda dapat membuat metode Extension Berbeda Anda sendiri yang mengambil ekspresi lambda. Ini sebuah contoh

Buat kelas yang berasal dari antarmuka 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();
    }       
}

Kemudian buat metode Distinct Extension Anda

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

dan Anda dapat menggunakan metode ini untuk menemukan item yang berbeda

var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
            .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();
Dibangun kembali
sumber
Ekstensi ini tidak dapat menangani tipe objectdan object. Jika kedua objectadalah stringmasih mengembalikan duplikasi baris. Coba FirstNameketik isof objectdan tetapkan dengan yang sama di stringsana.
CallMeLaNN
0

Jika Alphadan Bravokeduanya mewarisi dari kelas umum, Anda akan dapat menentukan pemeriksaan kesetaraan di kelas induk dengan menerapkan IEquatable<T>.

Sebagai contoh:

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
sumber
jadi jika Anda menggunakan sebagai properti dari kelas jenis anonim Anda yang mengimplementasikan IEquatable <T>, Persamaan disebut alih-alih perilaku default (periksa semua properti publik melalui refleksi?)
D_Guidi
0

Hai, di sana saya mendapat masalah yang sama dan saya menemukan solusinya. Anda harus mengimplementasikan antarmuka IEquatable atau cukup mengganti Metode (Equals & GetHashCode). Tapi ini bukan triknya, trik yang datang dalam Metode GetHashCode. Anda tidak boleh mengembalikan kode hash dari objek kelas Anda tetapi Anda harus mengembalikan hash dari properti yang ingin Anda bandingkan seperti itu.

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

Seperti yang Anda lihat saya mendapat kelas yang disebut orang mendapat 3 properti (Nama, Umur, IsEgyptian "Karena saya") Dalam GetHashCode saya mengembalikan hash properti Name bukan objek Person.

Cobalah dan ini akan berfungsi ISA. Terima kasih, Modather Sadik

Modather Sadik
sumber
1
GetHashCode harus menggunakan semua bidang dan properti yang sama yang digunakan dalam perbandingan untuk kesetaraan, bukan hanya salah satunya. yaitupublic override int GetHashCode() { return this.Name.GetHashCode() ^ this.Age.GetHashCode() ^ this.IsEgyptian.GetHashCode(); }
JG di SD
Untuk informasi tentang menghasilkan algoritma hash yang baik: stackoverflow.com/questions/263400/…
JG di SD
0

Agar dapat bekerja di VB.NET, Anda perlu menentukan Keykata kunci sebelum setiap properti dalam jenis anonim, seperti ini:

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

Saya berjuang dengan ini, saya pikir VB.NET tidak mendukung fitur jenis ini, tetapi sebenarnya tidak.

Alisson
sumber