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;
Jamie
quelle
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.

Jon Skeet
quelle
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);
Joel Mueller
quelle
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);
}
Matthew Abbott
quelle
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.

xmedeko
quelle
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);
Nick N.
quelle
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 :)

Rune FS
quelle
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());
}
Jamie
quelle
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);
}
StephenD
quelle