Pasa un solo elemento como IEnumerable


¿Hay una forma común de pasar un solo elemento de tipo T a un método que espera un parámetro IEnumerable<T>? El lenguaje es C#, framework versión 2.0.

Actualmente estoy usando un método helper (es. Net 2.0, así que tengo un montón de métodos helper de casting/projecting similares a LINQ), pero esto parece tonto:

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

Otra forma sería, por supuesto, crear y rellenar un List<T> o un Array y pasarlo en lugar de IEnumerable<T>.

[Editar] Como método de extensión podría ser nombrado:

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

¿Me estoy perdiendo algo aquí?

[Edit2] Encontramos que someObject.Yield() (como @Peter sugirió en los comentarios a continuación) es el mejor nombre para este método de extensión, principalmente por brevedad, así que aquí está junto con el comentario XML si alguien quiere tomarlo:

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;
    }
}
Author: Groo, 2009-10-16

15 answers

Su método helper es la forma más limpia de hacerlo, IMO. Si pasa una lista o una matriz, entonces una pieza de código sin escrúpulos podría emitirla y cambiar el contenido, lo que llevaría a un comportamiento extraño en algunas situaciones. Podrías usar una colección de solo lectura, pero es probable que eso implique aún más envoltorio. Creo que tu solución es lo mejor que se puede.

 81
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-16 12:49:05

Bueno, si el método espera un IEnumerable tienes que pasar algo que es una lista, incluso si contiene un solo elemento.

Pasando

new T[] { item }

Como el argumento debería ser suficiente creo

 101
Author: Mario F,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-16 12:45:19

En C# 3.0 puede utilizar el Sistema.Linq.Clase enumerable:

// using System.Linq

Enumerable.Repeat(item, 1);

Esto creará un nuevo IEnumerable que sólo contiene el elemento.

 93
Author: luksan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-22 19:36:24

En C # 3 (sé que dijiste 2), puedes escribir un método de extensión genérico que podría hacer que la sintaxis sea un poco más aceptable:

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

El código del cliente es entonces item.ToEnumerable().

 25
Author: Ðаn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-19 19:58:45

Me sorprende que nadie haya sugerido una nueva sobrecarga del método con un argumento de tipo T para simplificar la API del cliente.

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

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

Ahora su código de cliente puede hacer esto:

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

O con una lista:

List<MyItem> itemList = new List<MyItem>();
Obj.DoSomething(itemList);
 9
Author: Joshua Starner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-05 16:15:53

Este método de ayuda funciona para item o muchos.

public static IEnumerable<T> ToEnumerable<T>(params T[] items)
{
    return items;
}    
 8
Author: duraid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-01 13:51:07

Como acabo de encontrar, y he visto que el usuario LukeH sugirió también, una forma sencilla y agradable de hacer esto es la siguiente:

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

Este patrón le permitirá llamar a la misma funcionalidad de muchas maneras: un solo elemento; varios elementos (separados por comas); una matriz; una lista; una enumeración, etc.

No estoy 100% seguro de la eficiencia de usar el método AsEnumerable, pero funciona un placer.

Actualización: La función AsEnumerable parece bastante eficiente! (referencia )

 7
Author: teppicymon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:01

O bien (como se ha dicho anteriormente)

MyMethodThatExpectsAnIEnumerable(new[] { myObject });

O

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

Como nota al margen, la última versión también puede ser agradable si desea una lista vacía de un objeto anónimo, por ejemplo,

var x = MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(new { a = 0, b = "x" }, 0));
 5
Author: erikkallen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-16 13:22:21

Esto puede no ser mejor, pero es algo genial:

Enumerable.Range(0, 1).Select(i => item);
 4
Author: Ken Lange,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-23 13:49:42

Esto es un 30% más rápido que yield o Enumerable.Repeat cuando se usa en foreach debido a esta optimización del compilador de C# , y del mismo rendimiento en otros 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);
    }
}

En esta prueba:

    // 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}");
    }
 4
Author: V.B.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:23

Aunque es excesivo para un método, creo que algunas personas pueden encontrar útiles las Extensiones Interactivas.

Las Extensiones Interactivas (Ix) de Microsoft incluyen el siguiente método.

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

Que se puede utilizar de la siguiente manera:

var result = EnumerableEx.Return(0);

Ix agrega una nueva funcionalidad que no se encuentra en los métodos de extensión Linq originales, y es un resultado directo de la creación de las Extensiones Reactivas (Rx).

Piensa, Linq Extension Methods + Ix = Rx para IEnumerable.

Puedes encontrar ambos Rx y Ix en CodePlex .

 3
Author: cwharris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-25 23:00:19

Estoy de acuerdo con los comentarios de @EarthEngine al post original, que es que 'AsSingleton' es un nombre mejor. Ver esta entrada de wikipedia. Entonces se deduce de la definición de singleton que si un valor nulo se pasa como un argumento que 'AsSingleton' debe devolver unEnumerable con un solo valor nulo en lugar de un emptyumerable vacío que resolvería el debate if (item == null) yield break;. Creo que la mejor solución es tener dos métodos: 'AsSingleton' y 'AsSingletonOrEmpty'; donde, en el caso que los valores null se pasa como argumento, 'AsSingleton" devuelve un único valor null y 'AsSingletonOrEmpty' devolverá un vacío IEnumerable. Así:

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

Entonces, estos serían, más o menos, análogos a los métodos de extensión 'First' y 'FirstOrDefault' en Iumerable, que simplemente se siente bien.

 3
Author: Jason Boyd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-10 16:35:15

IanG tiene un buen post sobre el tema, sugiriendo EnumerableFrom() como nombre y menciona que la discusión señala que Haskell y Rx lo llaman Return. IIRC F# lo llama Return too. F#'s Seq llama al operador singleton<'T>.

Tentador si estás preparado para ser centrado en C#es llamarlo Yield [aludiendo al yield return involucrado en realizarlo].

Si usted está interesado en los aspectos perf de la misma, James Michael Hare tiene un devolver cero o uno items post también que bien vale la pena escanear.

 2
Author: Ruben Bartelink,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-19 12:33:17

La forma más fácil que diría sería new T[]{item};; no hay sintaxis para hacer esto. El equivalente más cercano que se me ocurre es la palabra clave params, pero por supuesto que requiere tener acceso a la definición del método y solo se puede usar con matrices.

 1
Author: FacticiusVir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-16 12:48:56

Llego un poco tarde a la fiesta, pero compartiré mi camino de todos modos. Mi problema era que quería enlazar el ItemSource o un WPF TreeView a un solo objeto. La jerarquía se ve así:

Proyecto > Parcela(es) > Habitación(es)

Siempre iba a haber un solo Proyecto, pero todavía quería mostrar el proyecto en el Árbol, sin tener que pasar una Colección con solo ese objeto como algunos sugirieron.
Dado que sólo se puede pasar IEnumerable objetos como ItemSource decidí para hacer mi clase IEnumerable:

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

    ... 

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

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

Y crear mi propio Enumerador en consecuencia:

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

Esta no es probablemente la solución "más limpia", pero funcionó para mí.

EDITAR
Para mantener el principio de responsabilidad única como @ Groo señaló, creé una nueva clase 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 usé así

TreeView.ItemSource = new SingleItemWrapper(itemToWrap);

EDITAR 2
Corregí un error con el método MoveNext().

 0
Author: IDarkCoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-23 07:30:57