Pasar un solo elemento como IEnumerable

¿Existe una forma común de pasar un único elemento de tipo T a un método que espera un IEnumerable ? Language es C #, versión de framework 2.0.

Actualmente estoy usando un método de ayuda (es .NET 2.0, así que tengo un montón de métodos de ayuda de lanzamiento / proyección similares a LINQ), pero esto parece tonto:

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

Otra forma sería, por supuesto, crear y completar una List o una Array y pasarla en lugar de IEnumerable .

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

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

¿Me estoy perdiendo de algo?

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

 public static class IEnumerableExt { ///  /// Wraps this object instance into an IEnumerable<T> /// consisting of a single item. ///  ///  Type of the object.  ///  The instance that will be wrapped.  ///  An IEnumerable<T> consisting of a single item.  public static IEnumerable Yield(this T item) { yield return item; } } 

Su método de ayuda es la forma más limpia de hacerlo, IMO. Si transfiere una lista o una matriz, un fragmento de código poco escrupuloso podría convertirlo y cambiar el contenido, lo que generaría un comportamiento extraño en algunas situaciones. Podría usar una colección de solo lectura, pero eso probablemente implique aún más envoltura. Creo que su solución es lo mejor posible.

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

paso

 new T[] { item } 

como el argumento debería ser suficiente, creo

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.

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

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

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

Estoy sorprendido de que nadie sugiriera una nueva sobrecarga del método con un argumento de tipo T para simplificar la API del cliente.

 public void DoSomething(IEnumerable list) { // Do Something } public void DoSomething(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 itemList = new List(); Obj.DoSomething(itemList); 

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

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

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

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

Este patrón le permitirá llamar a la misma funcionalidad en una multitud de formas: 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 sí funciona.

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

O bien (como se dijo 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)); 

Esto puede no ser mejor, pero es genial:

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

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

Las extensiones interactivas (Ix) de Microsoft incluyen el siguiente método.

 public static IEnumerable Return(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 Linq originales, y es un resultado directo de la creación de las Extensiones Reactivas (Rx).

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

Puede encontrar tanto Rx como Ix en CodePlex .

Estoy de acuerdo con los comentarios de @EarthEngine en la publicación original, que es que ‘AsSingleton’ es un nombre mejor. Vea esta entrada de wikipedia . Luego se deduce de la definición de singleton que si se pasa un valor nulo como argumento, ‘AsSingleton’ debería devolver un IEnumerable con un único valor nulo en lugar de un IEnumerable vacío que resolvería la if (item == null) yield break; debate. Creo que la mejor solución es tener dos métodos: ‘AsSingleton’ y ‘AsSingletonOrEmpty’; donde, en 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 AsSingletonOrEmpty(this T source) { if (source == null) { yield break; } else { yield return source; } } public static IEnumerable AsSingleton(this T source) { yield return source; } 

Entonces, estos serían, más o menos, análogos a los métodos de extensión “Primero” y “Primero por defecto” en IEnumerable que se siente bien.

Esto es un 30% más rápido que el yield o Enumerable.Repeat cuando se usa en foreach debido a esta optimización del comstackdor C # y del mismo rendimiento en otros casos.

 public struct SingleSequence : IEnumerable { public struct SingleEnumerator : IEnumerator { private readonly SingleSequence _parent; private bool _couldMove; public SingleEnumerator(ref SingleSequence 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 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(i)) { sum += single; } } sw.Stop(); Console.WriteLine($"Elapsed {sw.ElapsedMilliseconds}"); Console.WriteLine($"Mops {100000.0 / sw.ElapsedMilliseconds * 1.0}"); } 

IanG tiene una buena publicación sobre el tema , sugiriendo EnumerableFrom() como el nombre y menciona que la discusión señala que Haskell y Rx lo llaman Return . IIRC F # lo llama Retorno también . F # ‘ Seq llama al operador singleton<'T> .

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

Si le interesan los aspectos del rendimiento, James Michael Hare tiene también una devolución de cero o una publicación que vale la pena escanear.

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

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

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

Siempre iba a haber un solo proyecto, pero aún así quería mostrar el proyecto en el árbol, sin tener que pasar una colección con solo un objeto en ella, como algunos sugirieron.
Como solo puede pasar objetos IEnumerable como ItemSource, decidí hacer mi clase IEnumerable:

 public class ProjectClass : IEnumerable { private readonly SingleItemEnumerator enumerator; ... public IEnumerator 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 : IEnumerator { 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 @Groo señaló, creé una nueva clase contenedora:

 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 : IEnumerable { private readonly SingleItemEnumerator enumerator; public SingleItemWrapper(T item) { this.enumerator = new SingleItemEnumerator(item); } public T Item => this.enumerator.Current; public IEnumerator GetEnumerator() => this.enumerator; IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } 

Que usé así

 TreeView.ItemSource = new SingleItemWrapper(itemToWrap); 

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