Passando um único item como IEnumerable <T>


377

Existe uma maneira comum de passar um único item do tipo Tpara um método que espera um IEnumerable<T>parâmetro? O idioma é C #, versão 2.0 do framework.

Atualmente, estou usando um método auxiliar (é .Net 2.0, então eu tenho vários métodos auxiliares de projeção / projeção semelhantes ao LINQ), mas isso parece bobo:

public static class IEnumerableExt
{
    // usage: IEnumerableExt.FromSingleItem(someObject);
    public static IEnumerable<T> FromSingleItem<T>(T item)
    {
        yield return item; 
    }
}

Outra maneira, é claro, seria criar e preencher um List<T>ou um Arraye passá-lo em vez de IEnumerable<T>.

[Editar] Como método de extensão, ele pode ser chamado:

public static class IEnumerableExt
{
    // usage: someObject.SingleItemAsEnumerable();
    public static IEnumerable<T> SingleItemAsEnumerable<T>(this T item)
    {
        yield return item; 
    }
}

Estou faltando alguma coisa aqui?

[Edit2] Descobrimos someObject.Yield()(como @ Peter sugeriu nos comentários abaixo) o melhor nome para esse método de extensão, principalmente por questões de concisão, então aqui está o comentário XML, se alguém quiser pegá-lo:

public static class IEnumerableExt
{
    /// <summary>
    /// Wraps this object instance into an IEnumerable&lt;T&gt;
    /// consisting of a single item.
    /// </summary>
    /// <typeparam name="T"> Type of the object. </typeparam>
    /// <param name="item"> The instance that will be wrapped. </param>
    /// <returns> An IEnumerable&lt;T&gt; consisting of a single item. </returns>
    public static IEnumerable<T> Yield<T>(this T item)
    {
        yield return item;
    }
}

6
Eu faria uma pequena modificação no corpo do método de extensão: if (item == null) yield break; Agora você não pode passar nulo, nem tirar proveito do padrão de objeto nulo (trivial) para IEnumerable. ( foreach (var x in xs)lida com um vazio xsmuito bem). Aliás, essa função é a unidade monádica da mônada da lista IEnumerable<T>e, dada a festa de amor da mônada na Microsoft, estou surpreso que algo assim não esteja no quadro em primeiro lugar.
Matt Enright

2
Para o método de extensão, você não deve nomeá-lo AsEnumerableporque já existe uma extensão interna com esse nome . (Quando Timplementa IEnumerable, por exemplo, string.)
Jon-Eric

22
Que tal nomear o método Yield? Nada supera a brevidade.
Philip Guin

4
Sugestões de nomes aqui. "SingleItemAsEnumerable" um pouco detalhado. "Rendimento" descreve a implementação em vez da interface - o que não é bom. Para um nome melhor, sugiro "AsSingleton", que corresponde ao significado exato do comportamento.
Earth Engine

4
Eu odeio o left==nullcheque aqui. Ele quebra as belezas do código e deixa o código mais flexível - e se algum dia você descobrir que precisa gerar um singleton com algo que pode ser nulo? Quero dizer, new T[] { null }não é o mesmo que new T[] {}, e um dia você pode precisar distingui-los.
Earth Engine

Respostas:


100

Seu método auxiliar é a maneira mais limpa de fazer isso, IMO. Se você passar uma lista ou uma matriz, um código inescrupuloso pode convertê-lo e alterar o conteúdo, levando a um comportamento estranho em algumas situações. Você pode usar uma coleção somente leitura, mas é provável que envolva ainda mais quebra automática. Eu acho que sua solução é a mais limpa possível.


bem se a lista / array é construído ad-hoc, suas extremidades escopo após a chamada de método, por isso não deve causar problemas
Mario F

Se enviado como uma matriz, como pode ser alterado? Eu acho que poderia ser convertido para uma matriz e depois alterar a referência para outra coisa, mas que bem isso faria? (Eu sou provavelmente faltando alguma coisa embora ...)
Svish

2
Suponha que você decidiu criar um enumerável para passar para dois métodos diferentes ... então o primeiro lançou-o em uma matriz e alterou o conteúdo. Em seguida, você o passa como argumento para outro método, sem saber da alteração. Não estou dizendo que é provável, apenas que ter algo mutável quando não precisa ser menos arrumado do que a imutabilidade naturla.
9139 Jon Skeet

3
Esta é uma resposta aceita e provavelmente lida, por isso acrescentarei minha preocupação aqui. Eu tentei esse método, mas isso interrompeu minha chamada de compilação anterior para myDict.Keys.AsEnumerable()onde myDictera do tipo Dictionary<CheckBox, Tuple<int, int>>. Depois de renomear o método de extensão para SingleItemAsEnumerable, tudo começou a funcionar. Não é possível converter IEnumerable <Dictionary <CheckBox, System.Tuple <int, int >>. KeyCollection> 'para' IEnumerable <CheckBox> '. Existe uma conversão explícita (você está perdendo um elenco?) Posso viver com um hack por um tempo, mas outros hackers avançados podem encontrar uma maneira melhor?
Hamish Grubijan 17/10/11

3
@HamishGrubijan: Parece que você queria apenas dictionary.Valuescomeçar. Basicamente, não está claro o que está acontecendo, mas sugiro que você inicie uma nova pergunta sobre isso.
Jon Skeet

133

Bem, se o método espera IEnumerableque você precise passar algo que é uma lista, mesmo que contenha apenas um elemento.

passagem

new[] { item }

como o argumento deve ser suficiente, eu acho


32
Eu acho que você pode até cair o T, uma vez que irá ser inferido (a menos que eu sou muito errado aqui: p)
Svish

2
Eu acho que não é inferido em C # 2.0.
Groo

4
"A língua é C #, quadro versão 2" O compilador C # 3 pode inferir T, eo código será totalmente compatível com .NET 2.
sisve

melhor resposta está no fundo ?!
Falco Alexander

2
@Svish Eu sugeri e editei a resposta para fazer exatamente isso - estamos em 2020 agora, então deve ser o padrão "imho"
OschtärEi 02/04

120

No C # 3.0, você pode utilizar a classe System.Linq.Enumerable:

// using System.Linq

Enumerable.Repeat(item, 1);

Isso criará um novo IEnumerable que contém apenas seu item.


26
Penso que esta solução tornará o código mais difícil de ler. :( Repita em um único elemento é bastante contra-intuitivo, você não acha?
Drahakar

6
Repita uma vez lê ok para mim. Solução muito mais simples sem ter que se preocupar em adicionar outro método de extensão e garantir que o espaço para nome seja incluído onde você quiser usá-lo.
si618

13
Sim, na verdade new[]{ item }, eu apenas me uso , apenas pensei que era uma solução interessante.
Luksan

7
Esta solução é dinheiro.
ProVega 11/11

IMHO esta deve ser a resposta aceita. É fácil de ler, conciso e é implementado em uma única linha na BCL, sem um método de extensão personalizado.
Ryan

35

No C # 3 (eu sei que você disse 2), você pode escrever um método de extensão genérico que pode tornar a sintaxe um pouco mais aceitável:

static class IEnumerableExtensions
{
    public static IEnumerable<T> ToEnumerable<T>(this T item)
    {
        yield return item;
    }
}

código do cliente é então item.ToEnumerable().


Obrigado, eu estou ciente disso (funciona no .Net 2.0 se eu usar o C # 3.0), eu queria saber se havia um mecanismo interno para isso.
Groo

12
Esta é uma alternativa muito boa. Eu só quero sugerir renomeando para ToEnumerableevitar bagunçar comSystem.Linq.Enumerable.AsEnumerable
Fede

3
Como observação lateral, já existe um ToEnumerablemétodo de extensão para IObservable<T>, portanto, esse nome de método também interfere nas convenções existentes. Honestamente, sugiro não usar um método de extensão, simplesmente porque é muito genérico.
Cwharris

14

Este método auxiliar funciona para itens ou muitos.

public static IEnumerable<T> ToEnumerable<T>(params T[] items)
{
    return items;
}    

11
Variação útil, pois suporta mais de um item. Eu gosto que ele confie na paramsconstrução da matriz, para que o código resultante tenha uma aparência limpa. Ainda não decidi se gosto mais ou menos do que new T[]{ item1, item2, item3 };para vários itens.
Página

Smart! Love it
Mitsuru Furuta

10

Estou surpreso que ninguém tenha sugerido uma nova sobrecarga do método com um argumento do tipo T para simplificar a API do cliente.

public void DoSomething<T>(IEnumerable<T> list)
{
    // Do Something
}

public void DoSomething<T>(T item)
{
    DoSomething(new T[] { item });
}

Agora, seu código de cliente pode fazer isso:

MyItem item = new MyItem();
Obj.DoSomething(item);

ou com uma lista:

List<MyItem> itemList = new List<MyItem>();
Obj.DoSomething(itemList);

19
Melhor ainda, você poderia ter, o DoSomething<T>(params T[] items)que significa que o compilador lidaria com a conversão de um único item em uma matriz. (Isso também permitiria que você passar em vários itens separados e, mais uma vez, o compilador iria lidar com convertê-los para uma matriz para você.)
LukeH

7

Qualquer um (como foi dito anteriormente)

MyMethodThatExpectsAnIEnumerable(new[] { myObject });

ou

MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(myObject, 1));

Como observação lateral, a última versão também pode ser boa se você quiser uma lista vazia de um objeto anônimo, por exemplo

var x = MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(new { a = 0, b = "x" }, 0));

Obrigado, embora Enumerable.Repeat seja novo no .Net 3.5. Parece se comportar de maneira semelhante ao método auxiliar acima.
Groo

7

Como acabei de encontrar e vi que o usuário LukeH sugeriu também, uma maneira simples e agradável de fazer isso é a seguinte:

public static void PerformAction(params YourType[] items)
{
    // Forward call to IEnumerable overload
    PerformAction(items.AsEnumerable());
}

public static void PerformAction(IEnumerable<YourType> items)
{
    foreach (YourType item in items)
    {
        // Do stuff
    }
}

Esse padrão permitirá que você chame a mesma funcionalidade de várias maneiras: um único item; vários itens (separados por vírgula); uma matriz; uma lista; uma enumeração etc.

Não tenho 100% de certeza sobre a eficiência do uso do método AsEnumerable, mas funciona muito bem.

Atualização: A função AsEnumerable parece bastante eficiente! ( referência )


Na verdade, você não precisa de .AsEnumerable()nada. Matriz YourType[]já implementada IEnumerable<YourType>. Mas minha pergunta estava se referindo ao caso em que apenas o segundo método (no seu exemplo) está disponível e você está usando o .NET 2.0 e deseja passar um único item.
Groo

2
Sim, é verdade, mas você vai achar que você pode obter um estouro de pilha ;-)
teppicymon

E para realmente responder a sua pergunta real (! Não o que eu estava realmente olhando para responder por mim mesmo), sim, você praticamente tem que convertê-lo para uma matriz de acordo com a resposta de Mario
teppicymon

2
Que tal apenas definir um IEnumerable<T> MakeEnumerable<T>(params T[] items) {return items;}método? Pode-se então usar isso com qualquer coisa que se espera IEnumerable<T>, para qualquer número de itens distintos. O código deve ser essencialmente tão eficiente quanto definir uma classe especial para retornar um único item.
Supercat 21/09

6

Embora seja um exagero para um método, acredito que algumas pessoas possam achar úteis as extensões interativas.

As extensões interativas (Ix) da Microsoft incluem o seguinte método.

public static IEnumerable<TResult> Return<TResult>(TResult value)
{
    yield return value;
}

Que pode ser utilizado assim:

var result = EnumerableEx.Return(0);

Ix adiciona nova funcionalidade não encontrada nos métodos de extensão Linq originais e é um resultado direto da criação das Extensões Reativas (Rx).

Pense, Linq Extension Methods+ Ix= Rxpara IEnumerable.

Você pode encontrar Rx e Ix no CodePlex .


5

Isso é 30% mais rápido que yieldou Enumerable.Repeatquando usado foreachdevido a essa otimização do compilador C # e com o mesmo desempenho em outros casos.

public struct SingleSequence<T> : IEnumerable<T> {
    public struct SingleEnumerator : IEnumerator<T> {
        private readonly SingleSequence<T> _parent;
        private bool _couldMove;
        public SingleEnumerator(ref SingleSequence<T> parent) {
            _parent = parent;
            _couldMove = true;
        }
        public T Current => _parent._value;
        object IEnumerator.Current => Current;
        public void Dispose() { }

        public bool MoveNext() {
            if (!_couldMove) return false;
            _couldMove = false;
            return true;
        }
        public void Reset() {
            _couldMove = true;
        }
    }
    private readonly T _value;
    public SingleSequence(T value) {
        _value = value;
    }
    public IEnumerator<T> GetEnumerator() {
        return new SingleEnumerator(ref this);
    }
    IEnumerator IEnumerable.GetEnumerator() {
        return new SingleEnumerator(ref this);
    }
}

neste teste:

    // Fastest among seqs, but still 30x times slower than direct sum
    // 49 mops vs 37 mops for yield, or c.30% faster
    [Test]
    public void SingleSequenceStructForEach() {
        var sw = new Stopwatch();
        sw.Start();
        long sum = 0;
        for (var i = 0; i < 100000000; i++) {
            foreach (var single in new SingleSequence<int>(i)) {
                sum += single;
            }
        }
        sw.Stop();
        Console.WriteLine($"Elapsed {sw.ElapsedMilliseconds}");
        Console.WriteLine($"Mops {100000.0 / sw.ElapsedMilliseconds * 1.0}");
    }

Obrigado, faz sentido criar uma estrutura para este caso para reduzir a carga do GC em loops apertados.
Groo

11
Tanto o recenseador e enumeráveis vão ficar encaixotado quando voltou ....
Stephen Drew

2
Não compare com o cronômetro. Use Benchmark.NET github.com/dotnet/BenchmarkDotNet
NN_

11
Apenas medi-lo e a new [] { i }opção é 3,2 vezes mais rápida que a sua opção. Além disso, sua opção é cerca de 1,2 vezes mais rápida que a extensão com yield return.
lorond 20/05/19

Você pode acelerar isso usando outra otimização de compilador C #: adicione um método SingleEnumerator GetEnumerator()que retorne sua estrutura. O compilador C # usará esse método (ele pesquisa via digitação de pato) em vez dos da interface e, assim, evita o boxe. Muitas coleções internas utilizam esse truque, como Lista . Nota: isso só funcionará quando você tiver uma referência direta SingleSequence, como no seu exemplo. Se você armazená-lo em uma IEnumerable<>variável, o truque não funcionará (o método da interface será usado)
enzi

5

IanG tem um bom post sobre o assunto , sugerindo EnumerableFrom()como o nome (e menciona que Haskell e Rx o chamam Return). O IIRC F # também chama Retorno . F # Seqchama o operadorsingleton<'T> .

Tentador, se você está preparado para ser cêntrico em C #, é chamá-lo Yield[aludindo aos yield returnenvolvidos na realização].

Se você está interessado nos aspectos de desempenho, James Michael Hare também tem uma postagem de zero ou um item retornando, o que vale uma varredura.


4

Isso pode não ser melhor, mas é bem legal:

Enumerable.Range(0, 1).Select(i => item);

Não é. É diferente.
Nawfal

Ewww. São muitos detalhes, apenas para transformar um item em um enumerável.
Página

11
Isso equivale a usar uma máquina Rube Goldberg para fazer o café da manhã. Sim, funciona, mas salta através de 10 aros para chegar lá. Uma coisa simples como essa pode ser o gargalo de desempenho, quando executado em um loop restrito executado milhões de vezes. Na prática, o aspecto desempenho não importa em 99% dos casos, mas pessoalmente ainda acho que é um exagero desnecessário.
enzi 24/03

4

Concordo com os comentários do @ EarthEngine na postagem original, que é 'AsSingleton' é um nome melhor. Veja esta entrada da Wikipedia . Da definição de singleton, segue-se que, se um valor nulo for passado como argumento de que 'AsSingleton' retornará um IEnumerable com um único valor nulo, em vez de um IEnumerable vazio, o que encerraria o if (item == null) yield break;debate. Eu acho que a melhor solução é ter dois métodos: 'AsSingleton' e 'AsSingletonOrEmpty'; onde, no caso de um nulo ser passado como argumento, 'AsSingleton' retornará um único valor nulo e 'AsSingletonOrEmpty' retornará um IEnumerable vazio. Como isso:

public static IEnumerable<T> AsSingletonOrEmpty<T>(this T source)
{
    if (source == null)
    {
        yield break;
    }
    else
    {
        yield return source;
    }
}

public static IEnumerable<T> AsSingleton<T>(this T source)
{
    yield return source;
}

Então, esses seriam, mais ou menos, análogos aos métodos de extensão 'First' e 'FirstOrDefault' no IEnumerable que parecem adequados.


2

A maneira mais fácil que eu diria seria new T[]{item};; não há sintaxe para fazer isso. O equivalente mais próximo que consigo pensar é a paramspalavra - chave, mas é claro que exige que você tenha acesso à definição do método e é utilizável apenas com matrizes.


2
Enumerable.Range(1,1).Select(_ => {
    //Do some stuff... side effects...
    return item;
});

O código acima é útil quando se usa como

var existingOrNewObject = MyData.Where(myCondition)
       .Concat(Enumerable.Range(1,1).Select(_ => {
           //Create my object...
           return item;
       })).Take(1).First();

No trecho de código acima, não há verificação vazia / nula e é garantido que apenas um objeto seja retornado sem medo de exceções. Além disso, por ser preguiçoso, o fechamento não será executado até que seja provado que não há dados existentes que atendam aos critérios.


Esta resposta foi automaticamente identificada como "baixa qualidade". Conforme explicado na ajuda ("Brevidade é aceitável, mas explicações mais completas são melhores."), Edite-o para informar ao OP o que ele está fazendo de errado, sobre o que é seu código.
Sandra Rossi

Vejo que a maior parte desta resposta, incluindo a parte da qual estou respondendo, foi editada mais tarde por outra pessoa. mas se a intenção do segundo trecho é fornecer um valor padrão se MyData.Where(myCondition)estiver vazia, que já é possível (e mais simples) com DefaultIfEmpty(): var existingOrNewObject = MyData.Where(myCondition).DefaultIfEmpty(defaultValue).First();. Isso pode ser simplificado ainda mais var existingOrNewObject = MyData.FirstOrDefault(myCondition);se você quiser default(T)e não com um valor personalizado.
BACON

0

Estou um pouco atrasado para a festa, mas vou compartilhar o meu caminho de qualquer maneira. Meu problema era que eu queria vincular o ItemSource ou um WPF TreeView a um único objeto. A hierarquia fica assim:

Projeto> Lote (s)> Quarto (s)

Sempre haveria apenas um projeto, mas eu ainda queria mostrar o projeto na árvore, sem ter que passar uma coleção com apenas esse objeto, como sugerido por alguns.
Como você só pode passar objetos IEnumerable como ItemSource, decidi fazer minha classe IEnumerable:

public class ProjectClass : IEnumerable<ProjectClass>
{
    private readonly SingleItemEnumerator<AufmassProjekt> enumerator;

    ... 

    public IEnumerator<ProjectClass > GetEnumerator() => this.enumerator;

    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}

E crie meu próprio enumerador de acordo:

public class SingleItemEnumerator : IEnumerator
{
    private bool hasMovedOnce;

    public SingleItemEnumerator(object current)
    {
        this.Current = current;
    }

    public bool MoveNext()
    {
        if (this.hasMovedOnce) return false;
        this.hasMovedOnce = true;
        return true;
    }

    public void Reset()
    { }

    public object Current { get; }
}

public class SingleItemEnumerator<T> : IEnumerator<T>
{
    private bool hasMovedOnce;

    public SingleItemEnumerator(T current)
    {
        this.Current = current;
    }

    public void Dispose() => (this.Current as IDisposable).Dispose();

    public bool MoveNext()
    {
        if (this.hasMovedOnce) return false;
        this.hasMovedOnce = true;
        return true;
    }

    public void Reset()
    { }

    public T Current { get; }

    object IEnumerator.Current => this.Current;
}

Provavelmente, essa não é a solução "mais limpa", mas funcionou para mim.

EDIT
Para manter o princípio de responsabilidade única, como o @Groo apontou, criei uma nova classe de wrapper:

public class SingleItemWrapper : IEnumerable
{
    private readonly SingleItemEnumerator enumerator;

    public SingleItemWrapper(object item)
    {
        this.enumerator = new SingleItemEnumerator(item);
    }

    public object Item => this.enumerator.Current;

    public IEnumerator GetEnumerator() => this.enumerator;
}

public class SingleItemWrapper<T> : IEnumerable<T>
{
    private readonly SingleItemEnumerator<T> enumerator;

    public SingleItemWrapper(T item)
    {
        this.enumerator = new SingleItemEnumerator<T>(item);
    }

    public T Item => this.enumerator.Current;

    public IEnumerator<T> GetEnumerator() => this.enumerator;

    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}

Que eu usei assim

TreeView.ItemSource = new SingleItemWrapper(itemToWrap);

EDIT 2
Corrigi um erro com o MoveNext()método.


A SingleItemEnumerator<T>classe faz sentido, mas fazer da classe um "item único IEnumerableem si" parece uma violação do princípio da responsabilidade única. Talvez isso torne a transmissão mais prática, mas eu ainda preferiria envolvê-lo conforme necessário.
Groo

0

Para ser apresentado em "Não necessariamente uma boa solução, mas ainda assim ... uma solução" ou "Truques LINQ estúpidos", você pode combinar Enumerable.Empty<>()com Enumerable.Append<>()...

IEnumerable<string> singleElementEnumerable = Enumerable.Empty<string>().Append("Hello, World!");

... ou Enumerable.Prepend<>()...

IEnumerable<string> singleElementEnumerable = Enumerable.Empty<string>().Prepend("Hello, World!");

Os dois últimos métodos estão disponíveis desde o .NET Framework 4.7.1 e o .NET Core 1.0.

Esta é uma solução viável se alguém realmente pretender usar os métodos existentes, em vez de escrever os seus próprios, embora eu esteja indeciso se isso é mais ou menos claro do que a Enumerable.Repeat<>()solução . Esse é definitivamente um código mais longo (em parte devido à inferência de parâmetro de tipo não ser possível Empty<>()) e, no entanto, cria o dobro de objetos enumeradores.

Completando este "Você sabia que esses métodos existem?" resposta, Array.Empty<>()poderia ser substituído Enumerable.Empty<>(), mas é difícil argumentar que torna a situação ... melhor.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.