วิธีการแปลงผลลัพธ์ linq เป็น HashSet หรือ HashedSet


196

ฉันมีคุณสมบัติในคลาสที่เป็น ISet ฉันกำลังพยายามดึงผลลัพธ์ของเคียวรี linq ลงในคุณสมบัตินั้น แต่ไม่สามารถหาวิธีทำได้

โดยทั่วไปมองหาส่วนสุดท้ายของสิ่งนี้:

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

ยังสามารถทำสิ่งนี้:

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

ฉันคิดว่าคุณควรจะออกจากHashedSetส่วนที่ มันเป็นเพียงความสับสนตั้งแต่C#และไม่ได้มีอะไรที่เรียกว่าLINQ HashedSet
Jogge

คำตอบจะอยู่ในกล่องคำตอบไม่ใช่ในคำถาม หากคุณต้องการที่จะตอบคำถามของคุณเองซึ่งเป็นไปตามกฎ SO อย่างสมบูรณ์แบบโปรดเขียนคำตอบสำหรับสิ่งนั้น
Adriaan

คำตอบ:


308

ฉันไม่คิดว่าจะมีสิ่งใดในตัวที่ทำสิ่งนี้ ... แต่มันง่ายมากที่จะเขียนวิธีการขยาย:

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

โปรดทราบว่าคุณต้องการวิธีส่วนขยาย (หรืออย่างน้อยวิธีทั่วไปของบางรูปแบบ) ที่นี่เพราะคุณอาจไม่สามารถแสดงประเภทของแบบTชัดเจน:

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

คุณไม่สามารถทำเช่นนั้นได้ด้วยการเรียกร้องให้ผู้HashSet<T>สร้าง เราพึ่งพาการอนุมานประเภทสำหรับวิธีการทั่วไปที่จะทำเพื่อเรา

ตอนนี้คุณสามารถเลือกที่จะตั้งชื่อToSetและส่งคืนISet<T>- แต่ฉันจะยึดติดกับToHashSetประเภทคอนกรีต ซึ่งสอดคล้องกับตัวดำเนินการ LINQ มาตรฐาน ( ToDictionary, ToList) และอนุญาตให้มีการขยายตัวในอนาคต (เช่นToSortedSet) คุณอาจต้องการระบุการโอเวอร์โหลดที่ระบุการเปรียบเทียบเพื่อใช้


2
จุดดีในประเภทที่ไม่ระบุชื่อ อย่างไรก็ตามประเภทไม่ระบุชื่อมีการแทนที่ที่มีประโยชน์GetHashCodeและEquals? ถ้าไม่พวกเขาจะไม่เก่งใน HashSet มากนัก
Joel Mueller

4
@Joel: ใช่พวกเขาทำ มิฉะนั้นสิ่งต่าง ๆ จะล้มเหลว เป็นที่ยอมรับว่าพวกเขาใช้ตัวเปรียบเทียบความเท่าเทียมกันเริ่มต้นสำหรับประเภทส่วนประกอบเท่านั้น แต่ก็มักจะดีพอ
Jon Skeet

2
@ JonSkeet - คุณรู้หรือไม่ว่ามีเหตุผลใดที่ทำให้สิ่งนี้ไม่ได้จัดทำในตัวดำเนินการ LINQ มาตรฐานควบคู่ไปกับ ToDictionary และ ToList? ฉันได้ยินมาว่ามีเหตุผลที่ดีหลายประการที่ทำให้สิ่งต่าง ๆ ถูกละเว้นคล้ายกับวิธีการ IE ที่นับไม่ได้ <T> .ForEach
Stephen Holt

1
@ สตีเฟ่นฮอลต์: ฉันเห็นด้วยกับการต่อต้านForEachแต่ToHashSetทำให้รู้สึกมากขึ้น - ฉันไม่ทราบว่ามีเหตุผลใดที่จะไม่ทำToHashSetนอกเหนือจากปกติ "ไม่พบประโยชน์บาร์" (ซึ่งฉันไม่เห็นด้วยกับ แต่ ... )
Jon Skeet

1
@Aaron: ไม่แน่ใจ ... อาจเป็นเพราะมันไม่ง่ายสำหรับผู้ให้บริการที่ไม่ใช่ LINQ-to-Objects ใช่ไหม (ฉันไม่สามารถจินตนาการได้ว่ามันจะเป็นไปไม่ได้ยอมรับได้)
Jon Skeet

75

เพียงแค่ส่ง IEnumerable ของคุณไปยัง constructor สำหรับ HashSet

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

2
คุณจะรับมือกับการฉายภาพซึ่งส่งผลในรูปแบบที่ไม่ระบุชื่อได้อย่างไร?
Jon Skeet

7
@ จอนฉันจะถือว่า YAGNI จนกว่าจะได้รับการยืนยัน
Anthony Pegram

1
เห็นได้ชัดว่าฉันไม่ โชคดีที่ในตัวอย่างนี้ฉันไม่ต้องการ
Joel Mueller

@ จอนประเภทที่ระบุโดย OP (บรรทัดแรกจะไม่แปลเป็นอย่างอื่น) ดังนั้นในกรณีนี้ฉันต้องยอมรับว่าเป็น YAGNI ในตอนนี้
Rune FS

2
@Rune FS: ในกรณีนี้ฉันสงสัยว่าโค้ดตัวอย่างนั้นไม่สมจริง มันหายากสวยที่จะมีประเภทพารามิเตอร์ T แต่รู้ว่ารายการของแต่ละเป็นไปได้bar.Items Tเมื่อพิจารณาว่ามันง่ายเพียงใดที่จะทำให้วัตถุประสงค์ทั่วไปนี้และประเภทที่ไม่ระบุตัวตนเกิดขึ้นบ่อยครั้งใน LINQ ฉันคิดว่ามันคุ้มค่าที่จะก้าวไปอีกขั้น
Jon Skeet


19

ตามที่ @Joel ระบุไว้คุณสามารถส่งต่อการนับจำนวนหากคุณต้องการวิธีการขยายคุณสามารถทำได้:

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

3

หากคุณต้องการเข้าถึงชุดเท่านั้นและแหล่งที่มาเป็นพารามิเตอร์ในวิธีการของคุณแล้วฉันจะไปด้วย

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

เหตุผลก็คือผู้ใช้อาจจะเรียกวิธีการของคุณด้วยวิธีที่มีISetอยู่แล้วดังนั้นคุณไม่จำเป็นต้องสร้างสำเนา


3

มีวิธีสร้างส่วนขยายใน. NET Framework และใน. NET core สำหรับการแปลงIEnumerableเป็น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);

ดูเหมือนว่าฉันไม่สามารถใช้งานได้ในไลบรารีมาตรฐาน. NET (ในขณะที่เขียน) ดังนั้นฉันจะใช้วิธีการขยายนี้:

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

2

ง่ายมาก :)

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

และใช่ T เป็นประเภทที่ระบุโดย OP :)


ไม่ได้ระบุอาร์กิวเมนต์ชนิด
Jon Skeet

ฮ่า ๆ ๆ ที่จะต้องลงคะแนนเสียงอย่างรุนแรงที่สุดในวันนี้ :) สำหรับฉันแล้วมันมีข้อมูลเดียวกันทุกประการ Beats ฉันว่าทำไมระบบอนุมานชนิดที่ไม่ได้อยู่แล้วอนุมานประเภทนั้นนะ :)
Rune FS

เลิกทำตอนนี้ ... แต่จริงๆแล้วมันมีความสำคัญมากเพราะคุณไม่ได้รับการอนุมานประเภท ดูคำตอบของฉัน
Jon Skeet

2
ฉันจำไม่ได้ว่าเห็นมันในบล็อกของ Eric ทำไมการอนุมานในตัวสร้างไม่ใช่ส่วนหนึ่งของข้อมูลจำเพาะคุณรู้หรือไม่ว่าทำไม
Rune FS

1

คำตอบของจอนนั้นสมบูรณ์แบบ ข้อแม้เดียวคือโดยใช้ HashedSet ของ NHibernate ฉันต้องแปลงผลลัพธ์เป็นชุดสะสม มีวิธีที่ดีที่สุดในการทำเช่นนี้?

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

หรือ

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

หรือฉันหายไปอย่างอื่น?


แก้ไข: นี่คือสิ่งที่ฉันทำ:

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

ToListToArrayโดยทั่วไปจะมีประสิทธิภาพมากกว่า มันจำเป็นต้องใช้งานICollection<T>หรือไม่?
Jon Skeet

แก้ไข. ฉันลงเอยด้วยวิธีการของคุณแล้วใช้สิ่งนั้นเพื่อทำ ToHashedSet (ดูการแก้ไขคำถามต้นฉบับ) ขอบคุณสำหรับความช่วยเหลือของคุณ.
เจมี่

0

แทนที่จะเป็นการแปลงอย่างง่ายของ IEnumerable ไปเป็น HashSet มันมักจะสะดวกในการแปลงคุณสมบัติของวัตถุอื่นเป็น HashSet คุณสามารถเขียนสิ่งนี้เป็น:

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

แต่ความชอบของฉันคือใช้ตัวเลือก:

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

พวกเขาทำสิ่งเดียวกันและอย่างที่สองก็สั้นลงอย่างเห็นได้ชัด แต่ฉันพบว่าสำนวนที่เหมาะกับสมองของฉันดีกว่า (ฉันคิดว่ามันเป็นเหมือน ToDictionary)

นี่คือวิธีการขยายที่จะใช้พร้อมการสนับสนุนตัวเปรียบเทียบแบบกำหนดเองเป็นโบนัส

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);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.