Pasar un solo elemento como IEnumerable <T>

377

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

Actualmente estoy usando un método auxiliar (es .Net 2.0, así que tengo un montón de métodos auxiliares de proyección / proyección similares a LINQ), pero esto parece una tontería:

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 completar una List<T>o una Arrayy pasarla en lugar de IEnumerable<T>.

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

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

¿Me estoy perdiendo de algo?

[Edit2] Encontramos someObject.Yield()(como @Peter sugirió en los comentarios a continuación) 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 obtenerlo:

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;
    }
}
Groo
fuente
66
Haría una ligera modificación en el cuerpo del método de extensión: if (item == null) yield break; ahora se le impide pasar nulo y aprovechar el patrón de objeto nulo (trivial) IEnumerable. ( foreach (var x in xs)maneja un vacío xsmuy bien). Por cierto, esta función es la unidad monádica de la mónada de la lista IEnumerable<T>, y dado el festival de amor de la mónada en Microsoft, me sorprende que algo así no esté en el marco en primer lugar.
Matt Enright el
2
Para el método de extensión, no debe nombrarlo AsEnumerableporque ya existe una extensión incorporada con ese nombre . (Cuando se Timplementa IEnumerable, por ejemplo,. string)
Jon-Eric
22
¿Qué tal nombrar el método Yield? Nada supera la brevedad.
Philip Guin
44
Sugerencias de nombres aquí. "SingleItemAsEnumerable" un poco detallado. "Rendimiento" describe la implementación en lugar de la interfaz, lo que no es bueno. Para un mejor nombre, sugiero "AsSingleton", que corresponde al significado exacto del comportamiento.
Earth Engine
44
Odio el left==nullcheque aquí. Rompe la belleza del código y evita que el código sea más flexible: ¿qué pasa si algún día resulta que necesitas generar un singleton con algo que puede ser nulo? Quiero decir, new T[] { null }no es lo mismo que new T[] {}, y algún día puede que necesites distinguirlos.
Earth Engine

Respuestas:

100

Su método de ayuda es la forma más limpia de hacerlo, en mi opinión. Si pasa una lista o una matriz, un código inescrupuloso podría emitirlo y cambiar el contenido, lo que llevaría a un comportamiento extraño en algunas situaciones. Podría usar una colección de solo lectura, pero es probable que implique aún más envoltura. Creo que su solución es tan clara como parece.

Jon Skeet
fuente
bueno, si la lista / matriz se construye ad-hoc, su alcance termina después de la llamada al método, por lo que no debería causar problemas
Mario F
Si se envía como una matriz, ¿cómo se puede cambiar? Supongo que podría lanzarse a una matriz y luego cambiar la referencia a otra cosa, pero ¿de qué serviría eso? (Probablemente me estoy perdiendo algo ...)
Svish
2
Suponga que decidió crear uno enumerable para pasar a dos métodos diferentes ... luego el primero lo convirtió en una matriz y cambió el contenido. Luego lo pasa como argumento a otro método, sin darse cuenta del cambio. No digo que sea probable, solo que tener algo mutable cuando no es necesario es menos ordenado que la inmutabilidad naturla.
Jon Skeet el
3
Esta es una respuesta aceptada, y es muy probable que se lea, por lo que agregaré mi preocupación aquí. Intenté este método, pero eso rompió mi llamada de compilación anterior a myDict.Keys.AsEnumerable()donde myDictera de tipo Dictionary<CheckBox, Tuple<int, int>>. Después de cambiar el nombre del método de extensión a SingleItemAsEnumerable, todo comenzó a funcionar. No se puede convertir IEnumerable <Dictionary <CheckBox, System.Tuple <int, int >>. KeyCollection> 'a' IEnumerable <CheckBox> '. Existe una conversión explícita (¿te estás perdiendo un elenco?) Puedo vivir con un hack por un tiempo, pero ¿pueden otros hackers de poder encontrar una mejor manera?
Hamish Grubijan
3
@HamishGrubijan: Parece que solo querías dictionary.Valuescomenzar. Básicamente no está claro qué está pasando, pero le sugiero que comience una nueva pregunta al respecto.
Jon Skeet
133

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

paso

new[] { item }

como el argumento debería ser suficiente creo

Mario F
fuente
32
Creo que incluso podrías dejar caer la T, ya que se deducirá (a menos que esté muy equivocado aquí: p)
Svish
2
Creo que no se infiere en C # 2.0.
Groo
44
"Language is C #, framework versión 2" El compilador C # 3 puede inferir T, y el código será totalmente compatible con .NET 2.
sisve
¿La mejor respuesta está en la parte inferior?
Falco Alexander
2
@Svish sugerí y edite la respuesta para hacer exactamente eso: estamos en 2020 ahora, por lo que debería ser el "estándar" en mi opinión
OschtärEi
120

En C # 3.0 puede utilizar la clase System.Linq.Enumerable:

// using System.Linq

Enumerable.Repeat(item, 1);

Esto creará un nuevo IEnumerable que solo contiene su artículo.

luksan
fuente
26
Creo que esta solución hará que el código sea más difícil de leer. :( Repetir en un solo elemento es bastante contra-intuitivo, ¿no crees?
Drahakar
66
Repetir una vez me dice ok. Una solución mucho más simple sin tener que preocuparse por agregar otro método de extensión y asegurarse de que el espacio de nombres esté incluido donde quiera usarlo.
si618
13
Sí, en realidad solo new[]{ item }me utilizo , pensé que era una solución interesante.
luksan
77
Esta solución es dinero.
ProVega
En mi humilde opinión, esta debería ser la respuesta aceptada. Es fácil de leer, conciso y se implementa en una sola línea en el BCL, sin un método de extensión personalizado.
Ryan
35

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().

Ðаn
fuente
Gracias, soy consciente de eso (funciona en .Net 2.0 si uso C # 3.0), me preguntaba si había un mecanismo incorporado para esto.
Groo
12
Esta es una muy buena alternativa. Solo quiero sugerir cambiar el nombre para ToEnumerableevitar meterse con élSystem.Linq.Enumerable.AsEnumerable
Fede
3
Como nota al margen, ya existe un ToEnumerablemétodo de extensión para IObservable<T>, por lo que este nombre de método también interfiere con las convenciones existentes. Honestamente, sugeriría no usar un método de extensión, simplemente porque es demasiado genérico.
cwharris el
14

Este método de ayuda funciona para artículos o muchos.

public static IEnumerable<T> ToEnumerable<T>(params T[] items)
{
    return items;
}    
duraid
fuente
1
Variación útil, ya que admite más de un elemento. Me gusta que depende paramsde construir la matriz, por lo que el código resultante tiene un aspecto limpio. No he decidido si me gusta más o menos que new T[]{ item1, item2, item3 };para varios artículos.
ToolmakerSteve
Inteligente ! Me encanta
Mitsuru Furuta
10

Me sorprende que nadie sugiera 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);
Joshua Starner
fuente
19
Aún mejor, podría tener lo DoSomething<T>(params T[] items)que significa que el compilador manejaría la conversión de un solo elemento a una matriz. (Esto también permitirá pasar en varios elementos separados y, de nuevo, el compilador se ocuparía de convertirlos en una matriz para usted.)
LukeH
7

Cualquiera de los dos (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));
erikkallen
fuente
Gracias, aunque Enumerable.Repeat es nuevo en .Net 3.5. Parece comportarse de manera similar al método de ayuda anterior.
Groo
7

Como acabo de encontrar, y he visto que el usuario LukeH también sugirió, una manera simple 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; elementos múltiples (separados por comas); una matriz; una lista; una enumeración, etc.

Sin embargo, no estoy 100% seguro de la eficacia del uso del método AsEnumerable, pero funciona de maravilla.

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

teppicymon
fuente
En realidad, no necesitas .AsEnumerable()nada. La matriz YourType[]ya se implementa IEnumerable<YourType>. Pero mi pregunta se refería al caso en el que solo está disponible el segundo método (en su ejemplo), y está utilizando .NET 2.0, y desea pasar un solo elemento.
Groo
2
Sí, lo hace, pero usted encontrará que usted puede conseguir un desbordamiento de pila ;-)
teppicymon
Y para responder realmente a su pregunta real (¡no es la que realmente estaba tratando de responder por mí mismo!), Sí, tiene que convertirla en una matriz según la respuesta de Mario
teppicymon
2
¿Qué tal simplemente definir un IEnumerable<T> MakeEnumerable<T>(params T[] items) {return items;}método? Entonces se podría usar eso con cualquier cosa que se esperara IEnumerable<T>, para cualquier número de elementos discretos. El código debería ser esencialmente tan eficiente como definir una clase especial para devolver un solo elemento.
supercat
6

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 así:

var result = EnumerableEx.Return(0);

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

Piensa, Linq Extension Methods+ Ix= RxparaIEnumerable .

Puede encontrar Rx e Ix en CodePlex .

cwharris
fuente
5

Esto es 30% más rápido que yieldo Enumerable.Repeatcuando se usa foreachdebido 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}");
    }
VB
fuente
Gracias, tiene sentido crear una estructura para este caso para reducir la carga de GC en bucles estrechos.
Groo
1
Tanto el enumerador como el enumerable serán encuadrados cuando regresen ...
Stephen Drew
2
No compares con el cronómetro. Utilice Benchmark.NET github.com/dotnet/BenchmarkDotNet
NN_
1
Solo lo medí y la new [] { i }opción es aproximadamente 3.2 veces más rápida que su opción. Además, su opción es aproximadamente 1.2 veces más rápida que la extensión con yield return.
lorond
Puede acelerar esto utilizando otra optimización del compilador de C #: agregue un método SingleEnumerator GetEnumerator()que devuelva su estructura. El compilador de C # usará este método (lo busca a través de la escritura de pato) en lugar de los de la interfaz y, por lo tanto, evita el boxeo. Muchas colecciones integradas utilizan este truco, como List . Nota: esto solo funcionará cuando tenga una referencia directa a SingleSequence, como en su ejemplo. Si lo almacena en una IEnumerable<>variable, entonces el truco no funcionará (se utilizará el método de interfaz)
enzi
5

IanG tiene una buena publicación sobre el tema , sugiriendo EnumerableFrom()su nombre (y menciona que Haskell y Rx lo llaman Return). IIRC F # lo llama Devolver también . F # 's Seqllama al operadorsingleton<'T> .

Tentador si estás preparado para ser centrado en C # es llamarlo Yield[aludiendo al yield returninvolucrado en darse cuenta].

Si está interesado en los aspectos de rendimiento, James Michael Hare también tiene una publicación de cero o uno de los artículos que vale la pena escanear.

Ruben Bartelink
fuente
4

Puede que esto no sea mejor, pero es genial:

Enumerable.Range(0, 1).Select(i => item);
Ken Lange
fuente
No es. Es diferente.
nawfal
Ewww. Eso es mucho detalle, solo para convertir un elemento en un elemento enumerable.
ToolmakerSteve
1
Esto es equivalente a usar una máquina Rube Goldberg para preparar el desayuno. Sí, funciona, pero salta a través de 10 aros para llegar allí. Una cosa simple como esta puede ser el cuello de botella de rendimiento cuando se realiza en un ciclo cerrado que se ejecuta millones de veces. En la práctica, el aspecto del rendimiento no importa en el 99% de los casos, pero personalmente sigo pensando que es una exageración innecesaria.
enzi
4

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

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 IEnumerable que simplemente se siente bien.

Jason Boyd
fuente
2

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 paramspalabra clave, pero, por supuesto, eso requiere que tenga acceso a la definición del método y solo se puede usar con matrices.

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

El código anterior es útil cuando se usa like

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

En el fragmento de código anterior no hay verificación vacía / nula, y se garantiza que solo se devolverá un objeto sin temor a excepciones. Además, debido a que es vago, el cierre no se ejecutará hasta que se demuestre que no hay datos existentes que cumplan los criterios.

frhack
fuente
Esta respuesta fue etiquetada automáticamente como "baja calidad". Como se explica en la ayuda ("La brevedad es aceptable, pero las explicaciones más completas son mejores"), edítela para decirle al OP qué está haciendo mal, de qué se trata su código.
Sandra Rossi
Veo que la mayor parte de esta respuesta, incluida la parte a la que estoy respondiendo, fue editada más tarde por otra persona. pero si la intención del segundo fragmento es proporcionar un valor predeterminado si MyData.Where(myCondition)está vacío, que ya es posible (y más simple) con DefaultIfEmpty(): var existingOrNewObject = MyData.Where(myCondition).DefaultIfEmpty(defaultValue).First();. Eso se puede simplificar aún más var existingOrNewObject = MyData.FirstOrDefault(myCondition);si lo desea default(T)y no un valor personalizado.
TOCINO
0

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

Proyecto> Parcela (s)> 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.
Como solo puede pasar objetos IEnumerable como ItemSource, decidí hacer que 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;
}

Probablemente esta no sea la solución "más limpia", pero funcionó para mí.

EDITAR
Para mantener el principio de responsabilidad única como señaló @Groo, creé una nueva clase de contenedor:

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

EDIT 2
Corrigí un error con el MoveNext()método.

IDarkCoder
fuente
La SingleItemEnumerator<T>clase tiene sentido, pero hacer de una clase un "elemento único IEnumerableen sí mismo" parece una violación del principio de responsabilidad única. Tal vez hace que pasarlo sea más práctico, pero aún así preferiría envolverlo según sea necesario.
Groo
0

Para ser archivado bajo "No necesariamente una buena solución, pero aún así ... una solución" o "Trucos estúpidos de LINQ", podría combinar Enumerable.Empty<>()con Enumerable.Append<>()...

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

... o Enumerable.Prepend<>()...

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

Los dos últimos métodos están disponibles desde .NET Framework 4.7.1 y .NET Core 1.0.

Esta es una solución viable si uno realmente tuviera la intención de usar los métodos existentes en lugar de escribir los suyos, aunque no estoy seguro si esto es más o menos claro que la Enumerable.Repeat<>()solución . Este es definitivamente un código más largo (en parte debido a que no es posible la inferencia de parámetros de tipo Empty<>()) y, sin embargo, crea el doble de objetos enumeradores.

Completando este "¿Sabía que existen estos métodos?" respuesta, Array.Empty<>()podría ser sustituido Enumerable.Empty<>(), pero es difícil argumentar que hace que la situación ... sea mejor.

TOCINO
fuente