Cara mengonversi hasil LINQ ke HashSet atau HashedSet

195

Saya memiliki properti di kelas yang merupakan ISet. Saya mencoba untuk mendapatkan hasil dari permintaan LINQ ke properti itu, tetapi tidak tahu bagaimana melakukannya.

Pada dasarnya, mencari bagian terakhir ini:

ISet<T> foo = new HashedSet<T>();
foo = (from x in bar.Items select x).SOMETHING;

Bisa juga melakukan ini:

HashSet<T> foo = new HashSet<T>();
foo = (from x in bar.Items select x).SOMETHING;
Jamie
sumber
Saya pikir Anda harus meninggalkan HashedSetbagian. Itu hanya membingungkan karena C#dan LINQtidak memiliki apa pun yang disebut HashedSet.
Jogge
Jawaban masuk dalam kotak jawaban, bukan dalam pertanyaan itu sendiri. Jika Anda ingin menjawab pertanyaan Anda sendiri, yang tidak masalah dengan aturan SO, harap tulis jawaban untuk itu.
Adriaan

Jawaban:

307

Saya tidak berpikir ada sesuatu yang dibangun di mana melakukan ini ... tetapi sangat mudah untuk menulis metode ekstensi:

public static class Extensions
{
    public static HashSet<T> ToHashSet<T>(
        this IEnumerable<T> source,
        IEqualityComparer<T> comparer = null)
    {
        return new HashSet<T>(source, comparer);
    }
}

Perhatikan bahwa Anda benar-benar menginginkan metode ekstensi (atau setidaknya metode generik dari beberapa bentuk) di sini, karena Anda mungkin tidak dapat mengekspresikan jenis Tsecara eksplisit:

var query = from i in Enumerable.Range(0, 10)
            select new { i, j = i + 1 };
var resultSet = query.ToHashSet();

Anda tidak dapat melakukannya dengan panggilan eksplisit ke HashSet<T> konstruktor. Kami mengandalkan inferensi jenis untuk metode umum untuk melakukannya bagi kami.

Sekarang Anda dapat memilih untuk memberi nama ToSetdan kembali ISet<T>- tapi saya akan tetap dengan ToHashSetdan jenis beton. Ini konsisten dengan operator LINQ standar ( ToDictionary, ToList) dan memungkinkan untuk ekspansi di masa depan (misalnya ToSortedSet). Anda mungkin juga ingin memberikan kelebihan menentukan perbandingan untuk digunakan.

Jon Skeet
sumber
2
Poin bagus tentang jenis anonim. Namun, apakah tipe anonim memiliki penggantian yang berguna GetHashCodedan Equals? Jika tidak, mereka tidak akan jauh lebih baik dalam HashSet ...
Joel Mueller
4
@ Joel: Ya, benar. Kalau tidak, semua hal akan gagal. Memang mereka hanya menggunakan pembanding kesetaraan default untuk jenis komponen, tapi itu biasanya cukup baik.
Jon Skeet
2
@JonSkeet - apakah Anda tahu jika ada alasan mengapa ini tidak disediakan di operator LINQ standar bersama ToDictionary dan ToList? Saya pernah mendengar bahwa sering ada alasan bagus mengapa hal-hal seperti itu dihilangkan, mirip dengan IEnumerable <T> yang hilang. Metode FOREach
Stephen Holt
1
@StephenHolt: Saya setuju dengan perlawanan terhadap ForEach, tetapi ToHashSetjauh lebih masuk akal - Saya tidak tahu alasan untuk tidak melakukan ToHashSetselain dari yang normal "tidak memenuhi bilah kegunaan" (yang saya tidak setuju, tapi saya tidak setuju, tapi ...)
Jon Skeet
1
@ Harun: Tidak yakin ... mungkin karena itu tidak akan sesederhana untuk penyedia non-LINQ-to-Objects? (Saya tidak bisa membayangkan itu tidak mungkin, diakui.)
Jon Skeet
75

Cukup berikan IEnumerable Anda ke konstruktor untuk HashSet.

HashSet<T> foo = new HashSet<T>(from x in bar.Items select x);
Joel Mueller
sumber
2
Bagaimana Anda mengatasi proyeksi yang menghasilkan tipe anonim?
Jon Skeet
7
@ Jon, saya akan menganggap itu YAGNI sampai dikonfirmasi sebaliknya.
Anthony Pegram
1
Jelas saya bukan. Untungnya, dalam contoh khusus ini, saya tidak perlu melakukannya.
Joel Mueller
@Jon jenisnya ditentukan oleh OP (baris pertama tidak akan mengkompilasi sebaliknya) jadi dalam hal ini saya harus setuju itu YAGNI untuk saat ini
Rune FS
2
@ Run FS: Dalam hal ini, saya menduga kode sampel tidak realistis. Sangat jarang memiliki parameter tipe T tetapi ketahuilah bahwa setiap item bar.Itemsakan menjadi T. Mengingat betapa mudahnya membuat tujuan umum ini, dan seberapa sering tipe anonim muncul di LINQ, saya pikir ada baiknya mengambil langkah tambahan.
Jon Skeet
19

Seperti yang dikatakan @ Joel, Anda bisa meneruskan enumerable Anda. Jika Anda ingin melakukan metode ekstensi, Anda dapat melakukan:

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> items)
{
    return new HashSet<T>(items);
}
Matthew Abbott
sumber
3

Jika Anda hanya perlu akses hanya baca ke set dan sumbernya adalah parameter untuk metode Anda, maka saya akan pergi dengan

public static ISet<T> EnsureSet<T>(this IEnumerable<T> source)
{
    ISet<T> result = source as ISet<T>;
    if (result != null)
        return result;
    return new HashSet<T>(source);
}

Alasannya adalah, bahwa pengguna dapat memanggil metode Anda dengan yang ISetsudah jadi Anda tidak perlu membuat salinan.

xmedeko
sumber
3

Ada metode ekstensi yang dibangun dalam kerangka .NET dan inti .NET untuk mengonversikannya IEnumerablemenjadi HashSet: https://docs.microsoft.com/en-us/dotnet/api/?term=ToHashSet

public static System.Collections.Generic.HashSet<TSource> ToHashSet<TSource> (this System.Collections.Generic.IEnumerable<TSource> source);

Tampaknya saya belum bisa menggunakannya di .NET standard libraries (saat penulisan). Jadi saya menggunakan metode ekstensi ini:

    [Obsolete("In the .NET framework and in NET core this method is available, " +
              "however can't use it in .NET standard yet. When it's added, please remove this method")]
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source, IEqualityComparer<T> comparer = null) => new HashSet<T>(source, comparer);
Nick N.
sumber
2

Itu cukup sederhana :)

var foo = new HashSet<T>(from x in bar.Items select x);

dan ya T adalah tipe yang ditentukan oleh OP :)

Rune FS
sumber
Itu tidak menentukan argumen tipe.
Jon Skeet
Lol yang harus menjadi suara turun paling keras hari ini :). Bagi saya ada info yang persis sama sekarang. Mengalahkan saya mengapa sistem inferensi tipe belum menyimpulkan tipe itu :)
Rune FS
Batalkan sekarang ... tetapi sebenarnya cukup signifikan karena Anda tidak mendapatkan inferensi jenis. Lihat jawaban saya.
Jon Skeet
2
Saya tidak ingat melihatnya di blog Eric mengapa kesimpulan tentang konstruktor bukan bagian dari spesifikasi, tahukah Anda mengapa?
Rune FS
1

Jawaban Jon sempurna. Satu-satunya peringatan adalah bahwa, menggunakan HashedSet NHibernate, saya perlu mengubah hasilnya menjadi koleksi. Apakah ada cara optimal untuk melakukan ini?

ISet<string> bla = new HashedSet<string>((from b in strings select b).ToArray()); 

atau

ISet<string> bla = new HashedSet<string>((from b in strings select b).ToList()); 

Atau apakah saya melewatkan sesuatu yang lain?


Sunting: Inilah yang akhirnya saya lakukan:

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
{
    return new HashSet<T>(source);
}

public static HashedSet<T> ToHashedSet<T>(this IEnumerable<T> source)
{
    return new HashedSet<T>(source.ToHashSet());
}
Jamie
sumber
ToListumumnya lebih efisien daripada ToArray. Apakah itu hanya perlu diimplementasikan ICollection<T>?
Jon Skeet
Benar. Saya akhirnya menggunakan metode Anda, lalu menggunakannya untuk melakukan ToHashedSet (lihat edit pada pertanyaan awal). Terima kasih atas bantuan Anda.
Jamie
0

Daripada konversi sederhana dari IEnumerable ke HashSet, sering kali lebih mudah untuk mengubah properti objek lain menjadi HashSet. Anda dapat menulis ini sebagai:

var set = myObject.Select(o => o.Name).ToHashSet();

tetapi, preferensi saya adalah menggunakan penyeleksi:

var set = myObject.ToHashSet(o => o.Name);

Mereka melakukan hal yang sama, dan yang kedua jelas lebih pendek, tetapi saya menemukan idiom tersebut lebih cocok dengan otak saya (saya menganggapnya seperti ToDictionary).

Inilah metode ekstensi untuk digunakan, dengan dukungan untuk pembanding khusus sebagai bonus.

public static HashSet<TKey> ToHashSet<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> selector,
    IEqualityComparer<TKey> comparer = null)
{
    return new HashSet<TKey>(source.Select(selector), comparer);
}
StephenD
sumber