Come convertire i risultati di linq in HashSet o HashedSet


195

Ho una proprietà su una classe che è un ISet. Sto cercando di ottenere i risultati di una query linq in quella proprietà, ma non riesco a capire come farlo.

Fondamentalmente, cercando l'ultima parte di questo:

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

Potrebbe anche fare questo:

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

Penso che dovresti lasciare fuori la HashedSetparte. Da allora è solo fonte di confusione C#e LINQnon si fa chiamare nulla HashedSet.
Jogge,

Le risposte vanno nelle caselle di risposta, non nella domanda stessa. Se vuoi rispondere alla tua domanda, che è perfettamente soddisfacente dalle regole SO, scrivi una risposta per questo.
Adriaan,

Risposte:


307

Non penso che ci sia qualcosa di incorporato in questo che fa ... ma è davvero facile scrivere un metodo di estensione:

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

Nota che vuoi davvero un metodo di estensione (o almeno un metodo generico in qualche forma) qui, perché potresti non essere in grado di esprimere esplicitamente il tipo di T:

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

Non puoi farlo con una chiamata esplicita al HashSet<T>costruttore. Facciamo affidamento sull'inferenza del tipo per metodi generici per farlo per noi.

Ora si potrebbe scegliere di chiamarlo ToSete di ritorno ISet<T>- ma mi piacerebbe restare con ToHashSete il tipo concreto. Ciò è coerente con gli operatori LINQ standard ( ToDictionary, ToList) e consente espansioni future (ad es ToSortedSet.). È inoltre possibile fornire un sovraccarico specificando il confronto da utilizzare.


2
Buon punto sui tipi anonimi. Tuttavia, i tipi anonimi hanno sostituzioni utili di GetHashCodee Equals? In caso contrario, non saranno molto bravi in ​​un HashSet ...
Joel Mueller,

4
@ Joel: Sì, lo fanno. Altrimenti tutti i tipi di cose fallirebbero. È vero che usano solo i comparatori di uguaglianza predefiniti per i tipi di componente, ma di solito è abbastanza buono.
Jon Skeet,

2
@JonSkeet: sai se c'è qualche motivo per cui questo non viene fornito negli operatori LINQ standard insieme a ToDictionary e ToList? Ho sentito che ci sono spesso buoni motivi per cui tali cose vengono omesse, in modo simile al metodo IEnumerable <T> .ForEach mancante
Stephen Holt,

1
@StephenHolt: sono d'accordo con la resistenza ForEach, ma ToHashSetha molto più senso - non conosco alcun motivo per non fare ToHashSetaltro che il normale "non soddisfa la barra dell'utilità" (con cui non sarei d'accordo, ma ...)
Jon Skeet,

1
@Aaron: Non sono sicuro ... forse perché non sarebbe così semplice per i provider non LINQ-to-Objects? (Non posso immaginare che sarebbe impossibile, lo ammetto.)
Jon Skeet,

75

Basta passare IEnumerable nel costruttore per HashSet.

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

2
Come hai intenzione di far fronte a una proiezione che si traduce in un tipo anonimo?
Jon Skeet,

7
@Jon, suppongo che sia YAGNI fino a quando non sarà confermato diversamente.
Anthony Pegram,

1
Chiaramente no. Fortunatamente, in questo esempio particolare, non ne ho bisogno.
Joel Mueller,

@Jon il tipo è specificato da OP (la prima riga non compilerebbe altrimenti), quindi in questo caso devo ammettere che è YAGNI per ora
Rune FS

2
@Rune FS: In questo caso, sospetto che il codice di esempio non sia realistico. È abbastanza raro avere un parametro di tipo T ma sapere che ogni elemento di bar.Itemssarà T. Data la facilità con cui questo scopo generale e la frequenza con cui vengono generati tipi anonimi in LINQ, penso che valga la pena fare un passo in più.
Jon Skeet,


19

Come affermato da @Joel, puoi semplicemente passare il tuo enumerabile. Se vuoi fare un metodo di estensione, puoi fare:

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

3

Se hai bisogno solo dell'accesso in sola lettura al set e la fonte è un parametro per il tuo metodo, allora vorrei andare con

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

Il motivo è che gli utenti possono chiamare il tuo metodo con il ISetgià, quindi non è necessario creare la copia.


3

Esiste un metodo di estensione creato nel framework .NET e nel core .NET per la conversione IEnumerablein 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);

Sembra che non riesca ancora a usarlo nelle librerie standard .NET (al momento della stesura). Quindi uso questo metodo di estensione:

    [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

È abbastanza semplice :)

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

e sì T è il tipo specificato da OP :)


Ciò non specifica un argomento di tipo.
Jon Skeet,

Lol che deve essere il voto più duro del giorno :). Per me ci sono esattamente le stesse informazioni ora. Mi colpisce perché il sistema di inferenza del tipo non sta già inferendo quel tipo comunque :)
Rune FS

Annulla ora ... ma in realtà è piuttosto significativo proprio perché non si ottiene l'inferenza del tipo. Vedi la mia risposta
Jon Skeet,

2
Non ricordo di averlo visto sul blog di Eric, perché l'inferenza sui costruttori non fa parte delle specifiche, sai perché?
Rune FS

1

La risposta di Jon è perfetta. L'unica avvertenza è che, utilizzando HashedSet di NHibernate, devo convertire i risultati in una raccolta. C'è un modo ottimale per farlo?

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

o

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

O mi manca qualcos'altro?


Modifica: questo è quello che ho finito per fare:

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

ToListè generalmente più efficiente di ToArray. Deve solo implementare ICollection<T>?
Jon Skeet,

Corretta. Ho finito con il tuo metodo, quindi usando quello per fare il ToHashedSet (vedi modifica sulla domanda originale). Grazie per l'aiuto.
Jamie,

0

Anziché la semplice conversione di IEnumerable in un HashSet, è spesso conveniente convertire una proprietà di un altro oggetto in un HashSet. Puoi scrivere questo come:

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

ma la mia preferenza sarebbe quella di utilizzare i selettori:

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

Fanno la stessa cosa, e il secondo è ovviamente più breve, ma trovo che il linguaggio si adatti meglio al mio cervello (penso che sia come ToDictionary).

Ecco il metodo di estensione da utilizzare, con supporto per comparatori personalizzati come 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);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.