Como converter resultados linq em HashSet ou HashedSet

195

Eu tenho uma propriedade em uma classe que é um ISet. Estou tentando obter os resultados de uma consulta linq nessa propriedade, mas não consigo descobrir como fazer isso.

Basicamente, procurando a última parte disso:

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

Também poderia fazer isso:

HashSet<T> foo = new HashSet<T>();
foo = (from x in bar.Items select x).SOMETHING;
Jamie
fonte
Eu acho que você deveria deixar de fora a HashedSetparte. É confuso desde então C#e LINQnão tem nada chamado HashedSet.
Jogge 17/08/19
As respostas aparecem nas caixas de respostas, não na pergunta em si. Se você quiser responder sua própria pergunta, o que é perfeitamente adequado pelas regras do SO, escreva uma resposta para isso.
Adriaan

Respostas:

307

Eu não acho que exista algo embutido que faça isso ... mas é realmente fácil escrever um método de extensão:

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

Observe que você realmente deseja um método de extensão (ou pelo menos um método genérico de alguma forma) aqui, porque talvez não seja possível expressar Texplicitamente o tipo de :

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

Você não pode fazer isso com uma chamada explícita ao HashSet<T>construtor. Estamos confiando na inferência de tipo para métodos genéricos para fazer isso por nós.

Agora você pode escolher nomeá-lo ToSete retornar ISet<T>- mas eu continuaria com ToHashSeto tipo de concreto. Isso é consistente com os operadores LINQ padrão ( ToDictionary, ToList) e permite expansão futura (por exemplo ToSortedSet). Você também pode fornecer uma sobrecarga especificando a comparação a ser usada.

Jon Skeet
fonte
2
Bom argumento sobre tipos anônimos. No entanto, os tipos anônimos têm substituições úteis de GetHashCodee Equals? Se não, eles não vai ser muito bom em um HashSet ...
Joel Mueller
4
@ Joel: Sim, eles fazem. Caso contrário, todos os tipos de coisas falhariam. É certo que eles usam apenas os comparadores de igualdade padrão para os tipos de componentes, mas isso geralmente é bom o suficiente.
Jon Skeet
2
@ JonSkeet - você sabe se existe alguma razão para isso não ser fornecido nos operadores LINQ padrão, juntamente com ToDictionary e ToList? Ouvi dizer que muitas vezes há boas razões para que tais coisas são omitidos, semelhante ao que falta o método IEnumerable <T> .ForEach
Stephen Holt
1
@StephenHolt: Eu concordo com a resistência ForEach, mas ToHashSetfaz muito mais sentido - eu não tenho nenhuma razão para não fazer ToHashSetoutra coisa senão o normal "não atende à barra de utilidade" (com a qual eu discordo, mas ...)
Jon Skeet
1
@ Aaron: Não tenho certeza ... possivelmente porque não seria tão simples para fornecedores que não são do LINQ-to-Objects? (Eu não posso imaginar que seria inviável, é certo.)
Jon Skeet
75

Basta passar seu IEnumerable para o construtor do HashSet.

HashSet<T> foo = new HashSet<T>(from x in bar.Items select x);
Joel Mueller
fonte
2
Como você vai lidar com uma projeção que resulta em um tipo anônimo?
Jon Skeet
7
@ Jon, eu diria que é YAGNI até confirmado o contrário.
Anthony Pegram
1
Claramente não sou. Felizmente, neste exemplo em particular, não preciso.
Joel Mueller
@ Jon do tipo é especificado pelo OP (a primeira linha não iria compilador de outra forma) para que, neste caso, eu tenho que concordar que de YAGNI por agora
Rune FS
2
@ Runune FS: Neste caso, suspeito que o código de exemplo não seja realista. É muito raro ter um parâmetro do tipo T, mas saiba que cada item bar.Itemsserá T. Dado o quão fácil é tornar esse objetivo geral e a frequência com que tipos anônimos surgem no LINQ, acho que vale a pena dar um passo extra.
Jon Skeet
19

Como o @Joel afirmou, você pode simplesmente passar o seu enumerável. Se você deseja fazer um método de extensão, pode:

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

Se você precisa apenas acessar somente leitura ao conjunto e a fonte é um parâmetro para o seu método, então eu usaria

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

O motivo é que os usuários podem chamar seu método com o ISetjá, para que você não precise criar a cópia.

xmedeko
fonte
3

Há um método de extensão criado na estrutura .NET e no núcleo .NET para converter um IEnumerableem 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 ainda não posso usá-lo nas bibliotecas padrão do .NET (no momento da redação). Então, eu uso este método de extensão:

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

Isso é bem simples :)

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

e sim T é o tipo especificado por OP :)

Rune FS
fonte
Isso não especifica um argumento de tipo.
Jon Skeet
Lol que tem que ser a votação mais dura do dia :). Para mim, há exatamente a mesma informação agora. Bate-me por que o sistema de inferência de tipos não estiver inferir que tipo de qualquer maneira :)
Rune FS
Desfeito agora ... mas na verdade é bastante significativo precisamente porque você não recebe inferência de tipo. Veja minha resposta.
Jon Skeet
2
Não me lembro de tê-lo visto no blog de Eric por que a inferência em construtores não faz parte das especificações. Você sabe por quê?
Rune FS
1

A resposta de Jon é perfeita. A única ressalva é que, usando o HashedSet do NHibernate, preciso converter os resultados em uma coleção. Existe uma maneira ideal de fazer isso?

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 estou faltando outra coisa?


Edit: Isto é o que acabei fazendo:

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
fonte
ToListgeralmente é mais eficiente que ToArray. Precisa apenas implementar ICollection<T>?
Jon Skeet
Corrigir. Acabei indo com o seu método, em seguida, usando isso para fazer o ToHashedSet (veja editar na pergunta original). Obrigado pela ajuda.
Jamie
0

Em vez da simples conversão de IEnumerable em um HashSet, geralmente é conveniente converter uma propriedade de outro objeto em um HashSet. Você pode escrever isso como:

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

mas, minha preferência seria usar seletores:

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

Eles fazem a mesma coisa, e o segundo é obviamente mais curto, mas acho que o idioma se encaixa melhor no meu cérebro (acho que é como o ToDictionary).

Aqui está o método de extensão a ser usado, com suporte para comparadores personalizados como um bônus.

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
fonte