Làm cách nào để chuyển đổi kết quả linq sang Hashset hoặc HashingSet


195

Tôi có một tài sản trên một lớp là ISet. Tôi đang cố gắng để có được kết quả của một truy vấn linq vào thuộc tính đó, nhưng không thể tìm ra cách để làm điều đó.

Về cơ bản, tìm kiếm phần cuối cùng của điều này:

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

Cũng có thể làm điều này:

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

Tôi nghĩ bạn nên bỏ qua HashedSet-part. Nó chỉ là khó hiểu kể từ khi C#LINQkhông có bất cứ điều gì được gọi HashedSet.
Chạy bộ

Câu trả lời đi vào hộp câu trả lời, không phải trong chính câu hỏi. Nếu bạn muốn trả lời câu hỏi của riêng bạn, điều này hoàn toàn tốt theo quy tắc SO, vui lòng viết câu trả lời cho điều đó.
Adriaan

Câu trả lời:


307

Tôi không nghĩ có bất cứ thứ gì được xây dựng để làm điều này ... nhưng thực sự dễ dàng để viết một phương thức mở rộng:

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

Lưu ý rằng bạn thực sự muốn một phương thức mở rộng (hoặc ít nhất là một phương thức chung của một số hình thức) ở đây, bởi vì bạn có thể không thể diễn tả loại Trõ ràng:

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

Bạn không thể làm điều đó với một cuộc gọi rõ ràng đến nhà HashSet<T>xây dựng. Chúng tôi đang dựa vào suy luận kiểu cho các phương pháp chung để làm điều đó cho chúng tôi.

Bây giờ bạn có thể chọn đặt tên cho nó ToSetvà trả lại ISet<T>- nhưng tôi vẫn gắn bó ToHashSetvà loại cụ thể. Điều này phù hợp với các toán tử LINQ tiêu chuẩn ( ToDictionary, ToList) và cho phép mở rộng trong tương lai (ví dụ ToSortedSet). Bạn cũng có thể muốn cung cấp quá tải chỉ định so sánh sẽ sử dụng.


2
Điểm tốt về các loại ẩn danh. Tuy nhiên, các loại ẩn danh có ghi đè hữu ích GetHashCodeEqualskhông? Nếu không, họ sẽ không giỏi trong Hashset ...
Joel Mueller

4
@Joel: Vâng, họ làm. Nếu không tất cả các loại điều sẽ thất bại. Phải thừa nhận rằng họ chỉ sử dụng các bộ so sánh đẳng thức mặc định cho các loại thành phần, nhưng điều đó thường đủ tốt.
Jon Skeet

2
@JonSkeet - bạn có biết liệu có lý do nào khiến điều này không được cung cấp trong các toán tử LINQ tiêu chuẩn cùng với ToDipedia và ToList không? Tôi đã nghe nói rằng thường có những lý do chính đáng tại sao những thứ như vậy bị bỏ qua, tương tự như phương pháp <T> .TorEach bị thiếu
Stephen Holt

1
@StephenHolt: Tôi đồng ý với sự kháng cự ForEach, nhưng ToHashSetcó ý nghĩa hơn nhiều - Tôi không biết lý do nào để không làm gì ToHashSetkhác hơn là "không đáp ứng thanh hữu ích" thông thường (nhưng tôi không đồng ý, nhưng ...)
Jon Skeet

1
@Aaron: Không chắc chắn ... có thể vì nó sẽ không đơn giản đối với các nhà cung cấp không phải đối tượng LINQ? (Tôi không thể tưởng tượng nó sẽ không thể thực hiện được, phải thừa nhận.)
Jon Skeet

75

Chỉ cần chuyển IEnumerable của bạn vào hàm tạo cho Hashset.

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

2
Làm thế nào bạn sẽ đối phó với một phép chiếu dẫn đến một loại ẩn danh?
Jon Skeet

7
@Jon, tôi cho rằng đó là YAGNI cho đến khi được xác nhận khác.
Anthony Pegram

1
Rõ ràng là tôi không. May mắn thay, trong ví dụ cụ thể này, tôi không cần.
Joel Mueller

@Jon loại được chỉ định bởi OP (dòng đầu tiên sẽ không có trình biên dịch khác) vì vậy trong trường hợp này tôi phải đồng ý với nó là YAGNI ngay bây giờ
Rune FS

2
@Rune FS: Trong trường hợp này, tôi nghi ngờ mã mẫu không thực tế. Thật hiếm khi có một tham số loại T nhưng biết rằng mỗi mục bar.Itemssẽ có T. Do việc thực hiện mục đích chung này dễ dàng như thế nào và tần suất các loại ẩn danh xuất hiện trong LINQ, tôi nghĩ rằng đáng để thực hiện bước bổ sung.
Jon Skeet


19

Như @Joel đã nêu, bạn có thể chuyển vô số của mình vào. Nếu bạn muốn thực hiện một phương thức mở rộng, bạn có thể làm:

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

3

Nếu bạn chỉ cần truy cập chỉ đọc vào tập hợp và nguồn là một tham số cho phương thức của bạn, thì tôi sẽ đi với

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

Lý do là, người dùng có thể gọi phương thức của bạn bằng cách ISetđó vì vậy bạn không cần tạo bản sao.


3

Có một phương thức mở rộng được xây dựng trong khung .NET và trong lõi .NET để chuyển đổi IEnumerablethành 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);

Dường như tôi không thể sử dụng nó trong các thư viện chuẩn .NET (tại thời điểm viết bài). Vì vậy, sau đó tôi sử dụng phương pháp mở rộng này:

    [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

Điều đó khá đơn giản :)

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

và có T là loại được chỉ định bởi OP :)


Điều đó không chỉ định một đối số kiểu.
Jon Skeet

Lol đó đã được bình chọn xuống khắc nghiệt nhất trong ngày :). Đối với tôi có chính xác cùng một thông tin bây giờ. Đánh bại tôi tại sao hệ thống suy luận kiểu chưa suy ra kiểu đó :)
Rune FS

Hoàn tác ngay bây giờ ... nhưng nó thực sự khá quan trọng chính xác bởi vì bạn không bị suy luận kiểu. Xem câu trả lời của tôi.
Jon Skeet

2
Tôi không thể nhớ đã nhìn thấy nó trên blog của Eric tại sao suy luận về các nhà xây dựng không phải là một phần của thông số kỹ thuật bạn có biết tại sao không?
Rune FS

1

Câu trả lời của Jon là hoàn hảo. Nhắc nhở duy nhất là, bằng cách sử dụng HashingSet của NHibernate, tôi cần chuyển đổi kết quả thành một bộ sưu tập. Có một cách tối ưu để làm điều này?

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

hoặc là

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

Hay tôi đang thiếu thứ gì khác?


Chỉnh sửa: Đây là những gì tôi đã làm:

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

ToListnói chung là hiệu quả hơn ToArray. Nó chỉ cần thực hiện ICollection<T>?
Jon Skeet

Chính xác. Tôi đã kết thúc bằng phương pháp của bạn, sau đó sử dụng phương pháp đó để thực hiện ToHashset (xem chỉnh sửa câu hỏi ban đầu). Cảm ơn bạn đã giúp đỡ.
Jamie

0

Thay vì chuyển đổi đơn giản IEnumerable sang Hashset, việc chuyển đổi một thuộc tính của đối tượng khác thành Hashset thường rất thuận tiện. Bạn có thể viết điều này như:

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

nhưng, sở thích của tôi sẽ là sử dụng các công cụ chọn:

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

Họ làm điều tương tự, và cái thứ hai rõ ràng là ngắn hơn, nhưng tôi thấy thành ngữ phù hợp với bộ não của tôi hơn (tôi nghĩ nó giống như ToDipedia).

Đây là phương pháp mở rộng để sử dụng, với sự hỗ trợ cho các so sánh tùy chỉnh như một phần thưởng.

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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.