Cómo convertir resultados de linq a HashSet o HashedSet

195

Tengo una propiedad en una clase que es un ISet. Estoy tratando de obtener los resultados de una consulta linq en esa propiedad, pero no puedo entender cómo hacerlo.

Básicamente, buscando la última parte de esto:

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

También podría hacer esto:

HashSet<T> foo = new HashSet<T>();
foo = (from x in bar.Items select x).SOMETHING;
Jamie
fuente
Creo que deberías dejar de lado la parte HashedSet. Es confuso desde entonces C#y LINQno tengo nada llamado HashedSet.
Jogge
Las respuestas van en cuadros de respuestas, no en la pregunta misma. Si desea responder a su propia pregunta, que está perfectamente bien según las reglas de SO, escriba una respuesta para eso.
Adriaan

Respuestas:

307

No creo que haya nada incorporado que haga esto ... pero es realmente fácil escribir un método de extensión:

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

Tenga en cuenta que realmente desea un método de extensión (o al menos un método genérico de alguna forma) aquí, porque es posible que no pueda expresar el tipo Texplícitamente:

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

No puede hacer eso con una llamada explícita al HashSet<T> constructor. Confiamos en la inferencia de tipos para métodos genéricos que lo hagan por nosotros.

Ahora podría elegir nombrarlo ToSety regresar ISet<T>, pero me quedaría con ToHashSetel tipo concreto. Esto es consistente con los operadores estándar de LINQ ( ToDictionary, ToList) y permite una expansión futura (por ejemplo ToSortedSet). También puede proporcionar una sobrecarga que especifique la comparación a utilizar.

Jon Skeet
fuente
2
Buen punto sobre tipos anónimos. Sin embargo, ¿los tipos anónimos tienen anulaciones útiles de GetHashCodey Equals? Si no, no van a ser muy buenos en un HashSet ...
Joel Mueller
44
@ Joel: Sí, lo hacen. De lo contrario, todo tipo de cosas fallarían. Es cierto que solo usan los comparadores de igualdad predeterminados para los tipos de componentes, pero eso suele ser lo suficientemente bueno.
Jon Skeet
2
@ JonSkeet: ¿sabe si hay alguna razón por la cual esto no se proporciona en los operadores estándar de LINQ junto con ToDictionary y ToList? He oído que a menudo hay buenas razones por las cuales se omiten tales cosas, similar al método IEnumerable <T> .ForEach que falta
Stephen Holt
1
@StephenHolt: estoy de acuerdo con la resistencia ForEach, pero ToHashSettiene mucho más sentido: no conozco ninguna razón para no hacer ToHashSetotra cosa que la normal "no cumple con la barra de utilidad" (con la que no estoy de acuerdo, pero ...)
Jon Skeet
1
@Aaron: No estoy seguro ... ¿posiblemente porque no sería tan simple para los proveedores que no son de LINQ-to-Objects? (No puedo imaginar que sea inviable, lo admito)
Jon Skeet
75

Simplemente pase su IEnumerable al constructor de HashSet.

HashSet<T> foo = new HashSet<T>(from x in bar.Items select x);
Joel Mueller
fuente
2
¿Cómo vas a hacer frente a una proyección que resulta en un tipo anónimo?
Jon Skeet
77
@ Jon, supongo que es YAGNI hasta que se confirme lo contrario.
Anthony Pegram el
1
Claramente no lo soy. Afortunadamente, en este ejemplo en particular, no necesito hacerlo.
Joel Mueller
@Jon el tipo está especificado por OP (la primera línea no se compilaría de otra manera), así que en este caso tengo que aceptar que es YAGNI por ahora
Rune FS
2
@Rune FS: en este caso, sospecho que el código de muestra no es realista. Es bastante raro tener un parámetro de tipo T pero saber que cada elemento de bar.Itemsva a ser T. Dado lo fácil que es hacer este propósito general y la frecuencia con la que aparecen los tipos anónimos en LINQ, creo que vale la pena dar un paso más.
Jon Skeet
19

Como dijo @Joel, puede pasar su enumerable. Si desea hacer un método de extensión, puede hacer:

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

Si solo necesita acceso de solo lectura al conjunto y la fuente es un parámetro para su método, entonces iría 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);
}

La razón es que los usuarios pueden llamar a su método con el ISetya no necesita crear la copia.

xmedeko
fuente
3

Hay una compilación de método de extensión en .NET Framework y en .NET core para convertir una IEnumerablea 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);

Parece que todavía no puedo usarlo en las bibliotecas estándar de .NET (en el momento de la escritura). Entonces uso este método de extensión:

    [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.
fuente
2

Eso es bastante simple :)

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

y sí T es el tipo especificado por OP :)

Runa FS
fuente
Eso no especifica un argumento de tipo.
Jon Skeet
Lol que tiene que ser el voto más duro del día :). Para mí hay exactamente la misma información ahora. Me sorprende por qué el sistema de inferencia de tipos ya no infiere ese tipo de todos modos :)
Rune FS
Deshecho ahora ... pero en realidad es bastante significativo precisamente porque no obtienes inferencia de tipos. Mira mi respuesta.
Jon Skeet
2
No recuerdo haberlo visto en el blog de Eric, ¿por qué la inferencia sobre los constructores no forma parte de las especificaciones? ¿Sabes por qué?
Rune FS
1

La respuesta de Jon es perfecta. La única advertencia es que, usando HashedSet de NHibernate, necesito convertir los resultados en una colección. ¿Hay una manera óptima de hacer esto?

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 me estoy perdiendo algo más?


Editar: Esto es lo que terminé haciendo:

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
fuente
ToListes generalmente más eficiente que ToArray. ¿Solo necesita implementarse ICollection<T>?
Jon Skeet
Correcto. Terminé yendo con su método, luego usé eso para hacer el ToHashedSet (vea la edición en la pregunta original). Gracias por tu ayuda.
Jamie
0

En lugar de la simple conversión de IEnumerable a un HashSet, a menudo es conveniente convertir una propiedad de otro objeto en un HashSet. Podrías escribir esto como:

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

pero, mi preferencia sería usar selectores:

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

Hacen lo mismo, y el segundo es obviamente más corto, pero creo que el idioma se adapta mejor a mi cerebro (lo considero como ToDictionary).

Este es el método de extensión para usar, con soporte para comparadores personalizados como un bono.

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
fuente