Jak przekonwertować wyniki linq na HashSet lub HashedSet

196

Mam właściwość klasy, która jest ISet. Próbuję uzyskać wyniki zapytania linq w tej właściwości, ale nie mogę wymyślić, jak to zrobić.

Zasadniczo, szukając ostatniej części tego:

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

Mógłby to również zrobić:

HashSet<T> foo = new HashSet<T>();
foo = (from x in bar.Items select x).SOMETHING;
Jamie
źródło
Myślę, że powinieneś pominąć HashedSetczęść. Jest to po prostu mylące C#i odtąd LINQnic nie ma HashedSet.
Jogge
Odpowiedzi pojawiają się w polach odpowiedzi, a nie w samym pytaniu. Jeśli chcesz odpowiedzieć na własne pytanie, co jest w porządku według zasad SO, napisz na to odpowiedź.
Adriaan,

Odpowiedzi:

308

Nie sądzę, żeby było w tym coś wbudowanego ... ale bardzo łatwo jest napisać metodę rozszerzenia:

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

Zauważ, że naprawdę potrzebujesz tutaj metody rozszerzenia (lub przynajmniej ogólnej metody jakiejś formy), ponieważ możesz nie być w stanie wyrazić typu Tjawnie:

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

Nie możesz tego zrobić za pomocą jawnego wywołania HashSet<T>konstruktora. W tym celu opieramy się na wnioskowaniu o typ dla metod ogólnych.

Teraz już mógł wybrać, aby go wymienić ToSeti powrót ISet<T>- ale będę trzymać ToHashSeti rodzaju betonu. Jest to zgodne ze standardowymi operatorami LINQ ( ToDictionary, ToList) i umożliwia przyszłą rozbudowę (np ToSortedSet.). Możesz także podać przeciążenie określające porównanie do użycia.

Jon Skeet
źródło
2
Dobra uwaga na anonimowe typy. Czy jednak typy anonimowe mają użyteczne zastąpienia GetHashCodei Equals? Jeśli nie, nie będą zbyt dobre w HashSet ...
Joel Mueller
4
@Joel: Tak, robią. W przeciwnym razie zawiodłyby wszystkie rzeczy. Wprawdzie używają tylko domyślnych programów porównujących równość dla typów komponentów, ale zwykle jest to wystarczające.
Jon Skeet,
2
@JonSkeet - czy wiesz, czy istnieje jakiś powód, dla którego nie jest on dostarczany w standardowych operatorach LINQ obok ToDictionary i ToList? Słyszałem, że często istnieją dobre powody, dla których takie rzeczy są pomijane, podobnie jak brakująca IEnumerable <T> .ForEach metoda
Stephen Holt
1
@StephenHolt: Zgadzam się z oporem ForEach, ale ToHashSetma o wiele więcej sensu - nie znam żadnego powodu, aby nie robić ToHashSetinaczej niż normalne „nie spełnia paska użyteczności” (z czym nie chciałbym się zgodzić, ale ...)
Jon Skeet
1
@Aaron: Nie jestem pewien ... być może dlatego, że nie byłoby to takie proste dla dostawców innych niż LINQ-to-Objects? (Nie mogę sobie wyobrazić, że byłoby to niewykonalne.)
Jon Skeet
75

Po prostu przekaż swój IEnumerable do konstruktora dla HashSet.

HashSet<T> foo = new HashSet<T>(from x in bar.Items select x);
Joel Mueller
źródło
2
Jak poradzisz sobie z projekcją, która skutkuje anonimowym typem?
Jon Skeet,
7
@Jon, zakładam, że to YAGNI, dopóki nie zostanie potwierdzone inaczej.
Anthony Pegram,
1
Najwyraźniej nie jestem. Na szczęście w tym konkretnym przykładzie nie muszę tego robić.
Joel Mueller,
@Jon ten typ jest określony przez OP (pierwsza linia nie kompilowałaby się inaczej), więc w tym przypadku muszę się zgodzić, że to na razie YAGNI
Rune FS
2
@Rune FS: W tym przypadku podejrzewam, że przykładowy kod nie jest realistyczny. Jest to dość rzadki mieć parametr typu T, ale wiem, że każdy element bar.Itemsbędzie T. Biorąc pod uwagę, jak łatwo jest osiągnąć ten ogólny cel i jak często anonimowe typy pojawiają się w LINQ, myślę, że warto zrobić dodatkowy krok.
Jon Skeet,
19

Jak stwierdził @Joel, możesz po prostu przekazać swoje wyliczenie. Jeśli chcesz zastosować metodę rozszerzenia, możesz:

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> items)
{
    return new HashSet<T>(items);
}
Matthew Abbott
źródło
3

Jeśli potrzebujesz tylko dostępu do zestawu tylko do odczytu, a źródło jest parametrem twojej metody, to wybrałbym

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

Powodem jest to, że użytkownicy mogą wywołać twoją metodę z ISetjuż, więc nie musisz tworzyć kopii.

xmedeko
źródło
3

Istnieje metoda rozszerzenia wbudowana w środowisko .NET i .NET core do konwersji IEnumerablena 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);

Wygląda na to, że nie mogę go jeszcze używać w standardowych bibliotekach .NET (w momencie pisania). Więc używam tej metody rozszerzenia:

    [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.
źródło
2

To całkiem proste :)

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

i tak T jest typem określonym przez OP :)

Rune FS
źródło
To nie określa argumentu typu.
Jon Skeet,
Lol, który musi być najgorszym głosowaniem tego dnia :). Dla mnie są teraz dokładnie te same informacje. Uderza mnie, dlaczego system wnioskowania i tak już nie wnioskuje o tym typie :)
Rune FS
Cofnij teraz ... ale w rzeczywistości jest to dość znaczące właśnie dlatego , że nie można wnioskować o typie. Zobacz moją odpowiedź.
Jon Skeet,
2
Nie pamiętam, żeby widziałem na blogu Erica, dlaczego wnioskowanie o konstruktorach nie jest częścią specyfikacji. Wiesz dlaczego?
Run FS FS
1

Odpowiedź Jona jest idealna. Jedynym zastrzeżeniem jest to, że używając HashedSet NHibernate'a muszę przekonwertować wyniki na kolekcję. Czy istnieje optymalny sposób to zrobić?

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

lub

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

A może brakuje mi czegoś innego?


Edycja: Właśnie to skończyłem:

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
źródło
ToListjest ogólnie bardziej wydajny niż ToArray. Czy to wystarczy wdrożyć ICollection<T>?
Jon Skeet,
Poprawny. Skończyłem z twoją metodą, a następnie wykorzystałem ją do wykonania ToHashedSet (patrz edycja oryginalnego pytania). Dzięki za pomoc.
Jamie,
0

Zamiast zwykłej konwersji IEnumerable na HashSet, często wygodnie jest przekonwertować właściwość innego obiektu na HashSet. Możesz to napisać jako:

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

ale wolę używać selektorów:

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

Robią to samo, a druga jest oczywiście krótsza, ale uważam, że ten idiom lepiej pasuje do moich mózgów (myślę, że to jak ToDictionary).

Oto metoda rozszerzenia, której należy użyć, ze wsparciem dla niestandardowych programów porównujących jako bonusu.

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
źródło