Comment convertir les résultats linq en HashSet ou HashedSet

195

J'ai une propriété sur une classe qui est un ISet. J'essaie d'obtenir les résultats d'une requête linq dans cette propriété, mais je ne sais pas comment le faire.

Fondamentalement, à la recherche de la dernière partie de ceci:

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

Pourrait également faire ceci:

HashSet<T> foo = new HashSet<T>();
foo = (from x in bar.Items select x).SOMETHING;
Jamie
la source
Je pense que vous devriez laisser de côté la HashedSetpartie. Il est juste déroutant depuis C#et LINQn'a rien appelé HashedSet.
Jogge
Les réponses vont dans les cases de réponse, pas dans la question elle-même. Si vous souhaitez répondre à votre propre question, ce qui est tout à fait correct selon les règles de l'OS, veuillez écrire une réponse pour cela.
Adriaan

Réponses:

307

Je ne pense pas qu'il y ait quoi que ce soit intégré qui fasse cela ... mais il est vraiment facile d'écrire une méthode d'extension:

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

Notez que vous voulez vraiment une méthode d'extension (ou au moins une méthode générique d'une certaine forme) ici, car vous ne pourrez peut-être pas exprimer le type de Texplicitement:

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

Vous ne pouvez pas faire cela avec un appel explicite au HashSet<T>constructeur. Nous comptons sur l'inférence de type pour les méthodes génériques pour le faire pour nous.

Maintenant, vous pouvez choisir de le nommer ToSetet de revenir ISet<T>- mais je m'en tiendrai au ToHashSettype de béton. Ceci est cohérent avec les opérateurs LINQ standard ( ToDictionary, ToList) et permet une expansion future (par exemple ToSortedSet). Vous pouvez également vouloir fournir une surcharge spécifiant la comparaison à utiliser.

Jon Skeet
la source
2
Bon point sur les types anonymes. Cependant, les types anonymes ont-ils des remplacements utiles de GetHashCodeet Equals? Sinon, ils ne seront pas très bons dans un HashSet ...
Joel Mueller
4
@ Joel: Oui, ils le font. Sinon, toutes sortes de choses échoueraient. Certes, ils n'utilisent que les comparateurs d'égalité par défaut pour les types de composants, mais c'est généralement suffisant.
Jon Skeet
2
@ JonSkeet - savez-vous s'il y a une raison pour laquelle cela n'est pas fourni dans les opérateurs LINQ standard aux côtés de ToDictionary et ToList? J'ai entendu dire qu'il y a souvent de bonnes raisons pour lesquelles de telles choses sont omises, similaires à la méthode IEnumerable <T> .ForEach manquante
Stephen Holt
1
@StephenHolt: Je suis d'accord avec la résistance ForEach, mais ToHashSetest beaucoup plus logique - je ne connais aucune raison de ne pas faire ToHashSetautre que la normale "ne répond pas à la barre d'utilité" (ce avec quoi je ne suis pas d'accord, mais ...)
Jon Skeet
1
@Aaron: Pas sûr ... peut-être parce que ce ne serait pas aussi simple pour les fournisseurs non LINQ-to-Objects? (Je ne peux pas imaginer que ce serait irréalisable, certes.)
Jon Skeet
75

Passez simplement votre IEnumerable dans le constructeur de HashSet.

HashSet<T> foo = new HashSet<T>(from x in bar.Items select x);
Joel Mueller
la source
2
Comment allez-vous faire face à une projection qui se traduit par un type anonyme?
Jon Skeet
7
@Jon, je suppose que c'est YAGNI jusqu'à confirmation du contraire.
Anthony Pegram
1
De toute évidence, je ne le suis pas. Heureusement, dans cet exemple particulier, je n'en ai pas besoin.
Joel Mueller
@Jon le type est spécifié par OP (la première ligne ne compilerait pas autrement) donc dans ce cas je dois accepter que c'est YAGNI pour l'instant
Rune FS
2
@Rune FS: Dans ce cas, je soupçonne que l'exemple de code n'est pas réaliste. Il est assez rare d'avoir un paramètre de type T mais sachez que chaque élément de bar.Itemsva l'être T. Étant donné la facilité avec laquelle cet objectif est général et la fréquence à laquelle les types anonymes apparaissent dans LINQ, je pense que cela vaut la peine de faire un pas supplémentaire.
Jon Skeet
19

Comme l'a déclaré @Joel, vous pouvez simplement passer votre énumérable. Si vous voulez faire une méthode d'extension, vous pouvez faire:

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> items)
{
    return new HashSet<T>(items);
}
Matthew Abbott
la source
3

Si vous avez juste besoin d'accéder en lecture seule à l'ensemble et que la source est un paramètre de votre méthode, alors j'irais avec

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

La raison en est que les utilisateurs peuvent déjà appeler votre méthode avec le ISetafin que vous n'ayez pas besoin de créer la copie.

xmedeko
la source
3

Il existe une méthode d'extension construite dans le framework .NET et dans le noyau .NET pour convertir un IEnumerableen 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);

Il semble que je ne puisse pas encore l'utiliser dans les bibliothèques standard .NET (au moment de la rédaction). Alors j'utilise cette méthode d'extension:

    [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.
la source
2

C'est assez simple :)

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

et oui T est le type spécifié par OP :)

Rune FS
la source
Cela ne spécifie pas d'argument de type.
Jon Skeet
Lol qui doit être le vote le plus dur de la journée :). Pour moi, il y a exactement la même information maintenant. Je comprends pourquoi le système d'inférence de type n'infère pas déjà ce type de toute façon :)
Rune FS
Annulé maintenant ... mais c'est en fait assez important précisément parce que vous n'obtenez pas d'inférence de type. Voir ma réponse.
Jon Skeet
2
Je ne me souviens pas l'avoir vu sur le blog d'Eric, pourquoi l'inférence sur les constructeurs ne fait pas partie des spécifications, savez-vous pourquoi?
Rune FS
1

La réponse de Jon est parfaite. La seule mise en garde est que, en utilisant HashedSet de NHibernate, j'ai besoin de convertir les résultats en une collection. Existe-t-il un moyen optimal de procéder?

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

ou

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

Ou est-ce que je manque autre chose?


Edit: C'est ce que j'ai fini par faire:

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
la source
ToListest généralement plus efficace que ToArray. Faut-il simplement l'implémenter ICollection<T>?
Jon Skeet
Correct. J'ai fini par aller avec votre méthode, puis à l'utiliser pour faire le ToHashedSet (voir éditer sur la question d'origine). Merci de votre aide.
Jamie
0

Plutôt que la simple conversion de IEnumerable en HashSet, il est souvent pratique de convertir une propriété d'un autre objet en HashSet. Vous pouvez écrire ceci comme:

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

mais ma préférence serait d'utiliser des sélecteurs:

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

Ils font la même chose, et le second est évidemment plus court, mais je trouve que l'idiome correspond mieux à mon cerveau (je pense que c'est comme ToDictionary).

Voici la méthode d'extension à utiliser, avec prise en charge des comparateurs personnalisés en 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
la source