Tengo una biblioteca que solo acepta un tipo de colección inmutable patentada. Me gustaría tener una función que acepte una de estas colecciones y realice algunos cambios en esta colección devolviendo una nueva colección que contenga los cambios realizados.

Me gustaría usar una sintaxis LINQ en lugar de copiar esta colección en una Lista y viceversa.

Agregar operaciones es fácil para mí: concat el enumerable con otro. Pero, ¿qué pasa con Reemplazar (en el índice dado, devolver el valor dado en lugar del valor de ese IEnumerable), Insertar (en el índice dado, devolver el valor dado y luego continuar iterando sobre el IEnumerable) o Eliminar (en el índice dado, omitir el valor de IEnumerable )?

¿Hay funciones como esta disponibles en .NET Framework o en otra biblioteca? Si no, ¿cómo haría para implementar estas funciones?

6
Simon Farshid 29 dic. 2016 a las 19:58

3 respuestas

La mejor respuesta

Puede hacer sus propias extensiones para estas operaciones:

  • Añadir

    public static IEnumerable<T> Add<T>(this IEnumerable<T> enumerable, T value)
    {
        foreach (var item in enumerable)
            yield return item;
    
        yield return value;
    }
    

    O:

    public static IEnumerable<T> Add<T>(this IEnumerable<T> enumerable, T value)
    {
        return enumerable.Concat(new T[] { value });
    }
    
  • Insertar

    public static IEnumerable<T> Insert<T>(this IEnumerable<T> enumerable, int index, T value)
    {
        int current = 0;
        foreach (var item in enumerable)
        {
            if (current == index)
                yield return value;
    
            yield return item;
            current++;
        }
    }
    

    O

    public static IEnumerable<T> Insert<T>(this IEnumerable<T> enumerable, int index, T value)
    {
        return enumerable.SelectMany((x, i) => index == i ? new T[] { value, x } : new T[] { x });
    }
    
  • Reemplazar

    public static IEnumerable<T> Replace<T>(this IEnumerable<T> enumerable, int index, T value)
    {
        int current = 0;
        foreach (var item in enumerable)
        {
            yield return current == index ? value : item;
            current++;
        }
    }
    

    O

    public static IEnumerable<T> Replace<T>(this IEnumerable<T> enumerable, int index, T value)
    {
        return enumerable.Select((x, i) => index == i ? value : x);
    }
    
  • eliminar

    public static IEnumerable<T> Remove<T>(this IEnumerable<T> enumerable, int index)
    {
        int current = 0;
        foreach (var item in enumerable)
        {
            if (current != index)
                yield return item;
    
            current++;
        }
    }
    

    O

    public static IEnumerable<T> Remove<T>(this IEnumerable<T> enumerable, int index)
    {
        return enumerable.Where((x, i) => index != i);
    }
    

Entonces puedes hacer llamadas como esta:

IEnumerable<int> collection = new int[] { 1, 2, 3, 4, 5 };

var added = collection.Add(6);              // 1, 2, 3, 4, 5, 6
var inserted = collection.Insert(0, 0);     // 0, 1, 2, 3, 4, 5
var replaced = collection.Replace(1, 22);   // 1, 22, 3, 4, 5 
var removed = collection.Remove(2);         // 1, 2, 4, 5
11
Arturo Menchaca 29 dic. 2016 a las 17:39

La pregunta es un poco amplia, por lo que demostraré la posibilidad de un método Replace. No hay métodos para eso en el marco que reemplace algo en un IEnumerable, ya que IEnumerable debería representar una secuencia inmutable.

Entonces, una forma ingenua de devolver un nuevo IEnumerable con elementos reemplazados:

public static class Extensions
{
    public static IEnumerable<T> Replace<T>(this IEnumerable<T> source, T oldValue, T newValue)
    {
        return source.Select(element => element == oldValue ? newValue : element);
    }
}

Esto iterará a través de la secuencia fuente y devolverá los elementos fuente excepto aquellos que Equal el oldValue. Tenga en cuenta que esto utiliza el operador == y su funcionamiento depende del argumento de tipo para T.

También tenga en cuenta que esto utiliza ejecución diferida . La secuencia de origen solo se enumera cuando comienza a enumerar el IEnumerable resultante. Entonces, si cambia la secuencia de origen después de una llamada a Replace, la secuencia resultante también producirá este cambio.

Las implementaciones para Insert y Delete también son sencillas, aunque deberá contar un índice en la secuencia de origen.

1
René Vogt 29 dic. 2016 a las 17:08

IEnumerable es, por definición, una colección enumerable inmutable de elementos de un tipo dado. Inmutable significa que no puede modificarlo directamente y siempre debe crear una nueva instancia.

Sin embargo, puede usar la palabra clave yield para implementar este comportamiento, preferiblemente usando métodos de extensión.

Por ejemplo, reemplazar podría verse así:

public static IEnumerable<T> ReplaceAt<T>(this IEnumerable<T> collection, int index, T item)
{
    var currentIndex = 0;
    foreach (var originalItem in collection)
    {
        if (currentIndex != index)
        {
            //keep the original item in place
            yield return originalItem;
        }
        else
        {
            //we reached the index where we want to replace
            yield return item;
        }
        currentIndex++;
    }
}
1
Martin Zikmund 29 dic. 2016 a las 17:11