c# what - Pasando un solo elemento como IEnumerable <T>




8 Answers

Su método auxiliar es la forma más limpia de hacerlo, OMI. Si pasa en una lista o una matriz, entonces un trozo de código sin escrúpulos podría lanzarlo y cambiar el contenido, lo que provocaría un comportamiento extraño en algunas situaciones. Podría usar una colección de solo lectura, pero es probable que implique más envoltura. Creo que tu solución es tan clara como se puede.

is in

¿Hay una manera común de pasar un solo elemento de tipo T a un método que espera un IEnumerable<T> ? El lenguaje es C #, framework versión 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 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 rellenar una List<T> o un Array y 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 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, por lo que aquí está junto con el comentario XML si alguien quiere agarrarlo:

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



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.




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



Como acabo de encontrar, y he visto que el usuario LukeH también sugirió, una buena forma 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<YourType> 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 de usar el método AsEnumerable, pero funciona bien.

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




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



Estoy de acuerdo con los comentarios de @ EarthEngine en la publicación original, que dice que 'AsSingleton' es un nombre mejor. Vea esta entrada de wikipedia . Luego de la definición de singleton se deduce que si se pasa un valor nulo como argumento de que 'AsSingleton' debería devolver un IEnumerable con un solo valor nulo en lugar de un IEnumerable vacío que liquidaría el 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 solo 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 sienten bien.




IanG tiene una buena publicación 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 Volver también . F # 's Seq llama al operador singleton<'T> .

La tentación si está preparado para ser C #-céntrico es llamarlo Yield [que alude a la yield return del yield return involucrado en su realización].

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




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

Proyecto> Parcela (s)> Sala (s)

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 sugeridos.
Ya que solo puedes pasar objetos IEnumerable como ItemSource, decidí hacer que mi clase fuera 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 @Groo señaló, creé una nueva clase de envoltorio:

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 utilicé asi

TreeView.ItemSource = new SingleItemWrapper(itemToWrap);

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




Related

c# .net generics ienumerable