So konvertieren Sie Linq-Ergebnisse in HashSet oder HashedSet


195

Ich habe eine Eigenschaft für eine Klasse, die ein ISet ist. Ich versuche, die Ergebnisse einer Linq-Abfrage in diese Eigenschaft zu übertragen, kann aber nicht herausfinden, wie das geht.

Grundsätzlich auf der Suche nach dem letzten Teil davon:

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

Könnte auch das tun:

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

Ich denke, Sie sollten den Teil HashedSetweglassen. Es ist nur verwirrend da C#und LINQhat nichts genannt HashedSet.
Jogge

Die Antworten werden in Antwortfeldern und nicht in der Frage selbst angezeigt. Wenn Sie Ihre eigene Frage beantworten möchten, die nach SO-Regeln vollkommen in Ordnung ist, schreiben Sie bitte eine Antwort darauf.
Adriaan

Antworten:


307

Ich glaube nicht , dass irgendetwas eingebaut ist, das dies tut ... aber es ist wirklich einfach, eine Erweiterungsmethode zu schreiben:

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

Beachten Sie, dass Sie hier wirklich eine Erweiterungsmethode (oder zumindest eine generische Methode in irgendeiner Form) möchten, da Sie den Typ von möglicherweise nicht Texplizit ausdrücken können:

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

Sie können dies nicht mit einem expliziten Aufruf des HashSet<T>Konstruktors tun . Wir verlassen uns auf Typinferenz für generische Methoden, um dies für uns zu tun.

Jetzt könnten Sie es benennen ToSetund zurückkehren ISet<T>- aber ich würde bei ToHashSetund dem konkreten Typ bleiben . Dies steht im Einklang mit den Standard-LINQ-Operatoren ( ToDictionary, ToList) und ermöglicht eine zukünftige Erweiterung (z ToSortedSet. B. ). Möglicherweise möchten Sie auch eine Überladung bereitstellen, die den zu verwendenden Vergleich angibt.


2
Guter Punkt bei anonymen Typen. Haben anonyme Typen jedoch nützliche Überschreibungen von GetHashCodeund Equals? Wenn nicht, werden sie in einem HashSet nicht viel gut sein ...
Joel Mueller

4
@ Joel: Ja, das tun sie. Sonst würden alle möglichen Dinge scheitern. Zwar verwenden sie nur die Standard-Gleichheitsvergleiche für die Komponententypen, aber das ist normalerweise gut genug.
Jon Skeet

2
@ JonSkeet - wissen Sie, ob es einen Grund gibt, warum dies nicht in den Standard-LINQ-Operatoren neben ToDictionary und ToList enthalten ist? Ich habe gehört, dass es oft gute Gründe gibt, warum solche Dinge weggelassen werden, ähnlich der fehlenden IEnumerable <T> .ForEach-Methode
Stephen Holt

1
@StephenHolt: Ich bin mit dem Widerstand gegen einverstanden ForEach, aber es ToHashSetmacht viel mehr Sinn - ich kenne keinen Grund, etwas ToHashSetanderes als das normale "Erfüllt nicht die Nützlichkeitsleiste" zu tun (dem ich nicht zustimmen würde, aber ...)
Jon Skeet

1
@ Aaron: Nicht sicher ... möglicherweise, weil es für Nicht-LINQ-to-Objects-Anbieter nicht so einfach wäre? (Ich kann mir nicht vorstellen, dass es zugegebenermaßen nicht machbar wäre.)
Jon Skeet

75

Übergeben Sie einfach Ihre IEnumerable an den Konstruktor für HashSet.

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

2
Wie gehen Sie mit einer Projektion um, die zu einem anonymen Typ führt?
Jon Skeet

7
@ Jon, ich würde annehmen, dass das YAGNI ist, bis etwas anderes bestätigt wird.
Anthony Pegram

1
Klar bin ich nicht. Glücklicherweise muss ich in diesem Beispiel nicht.
Joel Mueller

@Jon der Typ wird von OP angegeben (die erste Zeile würde sonst nicht kompilieren), also muss ich in diesem Fall zustimmen, dass es YAGNI ist
Rune FS

2
@ Run FS: In diesem Fall vermute ich, dass der Beispielcode nicht realistisch ist. Es ist ziemlich selten, einen Typparameter T zu haben, aber man weiß, dass jedes Element von sein bar.Itemswird T. Angesichts der Tatsache, wie einfach es ist, diesen allgemeinen Zweck zu erfüllen, und wie häufig anonyme Typen in LINQ vorkommen, lohnt es sich meiner Meinung nach, den zusätzlichen Schritt zu tun.
Jon Skeet


19

Wie bei @Joel angegeben, können Sie Ihre Aufzählung einfach übergeben. Wenn Sie eine Erweiterungsmethode ausführen möchten, können Sie Folgendes tun:

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

3

Wenn Sie nur schreibgeschützten Zugriff auf das Set benötigen und die Quelle ein Parameter für Ihre Methode ist, würde ich mit gehen

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

Der Grund ist, dass die Benutzer Ihre Methode möglicherweise mit dem ISetbereits aufrufen, sodass Sie die Kopie nicht erstellen müssen.


3

Im .NET Framework und im .NET Core wird eine Erweiterungsmethode zum Konvertieren IEnumerablevon a in Folgendes erstelltHashSet : 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);

Es scheint, dass ich es noch nicht in .NET-Standardbibliotheken verwenden kann (zum Zeitpunkt des Schreibens). Also benutze ich diese Erweiterungsmethode:

    [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

Das ist ziemlich einfach :)

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

und ja T ist der von OP angegebene Typ :)


Das gibt kein Typargument an.
Jon Skeet

Lol, das muss die härteste Abstimmung des Tages sein :). Für mich gibt es jetzt genau die gleichen Informationen. Schlägt mich, warum das Typinferenzsystem diesen Typ sowieso nicht schon ableitet :)
Rune FS

Jetzt rückgängig machen ... aber es ist tatsächlich ziemlich wichtig, gerade weil Sie keine Typinferenz erhalten. Siehe meine Antwort.
Jon Skeet

2
Ich kann mich nicht erinnern, es auf Erics Blog gesehen zu haben, warum Rückschlüsse auf Konstruktoren nicht Teil der Spezifikationen sind. Weißt du warum?
Rune FS

1

Jons Antwort ist perfekt. Die einzige Einschränkung ist, dass ich mit HiberSet von NHibernate die Ergebnisse in eine Sammlung konvertieren muss. Gibt es einen optimalen Weg, dies zu tun?

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

oder

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

Oder fehlt mir noch etwas?


Edit: Das habe ich letztendlich gemacht:

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

ToListist in der Regel effizienter als ToArray. Muss es nur implementiert werden ICollection<T>?
Jon Skeet

Richtig. Am Ende habe ich mich für Ihre Methode entschieden und diese dann für das ToHashedSet verwendet (siehe Bearbeiten der ursprünglichen Frage). Danke für Ihre Hilfe.
Jamie

0

Anstelle der einfachen Konvertierung von IEnumerable in ein HashSet ist es häufig zweckmäßig, eine Eigenschaft eines anderen Objekts in ein HashSet zu konvertieren. Sie könnten dies schreiben als:

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

Ich würde jedoch lieber Selektoren verwenden:

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

Sie machen das Gleiche und die zweite ist offensichtlich kürzer, aber ich finde, dass die Redewendung besser zu meinem Gehirn passt (ich denke, es ist wie ToDictionary).

Hier ist die zu verwendende Erweiterungsmethode mit Unterstützung für benutzerdefinierte Vergleicher als 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);
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.