AddRange a una colección

109

Un compañero de trabajo me preguntó hoy cómo agregar un rango a una colección. Tiene una clase de la que hereda Collection<T>. Hay una propiedad de solo obtención de ese tipo que ya contiene algunos elementos. Quiere agregar los elementos de otra colección a la colección de propiedades. ¿Cómo puede hacerlo de una manera compatible con C # 3? (Tenga en cuenta la restricción sobre la propiedad de solo obtener, que evita soluciones como hacer Unión y reasignar).

Seguro, un foreach con Property. Agregar funcionará. Pero un List<T>AddRange al estilo sería mucho más elegante.

Es bastante fácil escribir un método de extensión:

public static class CollectionHelpers
{
    public static void AddRange<T>(this ICollection<T> destination,
                                   IEnumerable<T> source)
    {
        foreach (T item in source)
        {
            destination.Add(item);
        }
    }
}

Pero tengo la sensación de que estoy reinventando la rueda. No encontré nada similar en System.Linqni más en linq .

¿Mal diseño? ¿Solo llamar a Agregar? ¿Falta lo obvio?

TrueWill
fuente
5
Recuerde que la Q de LINQ es 'consulta' y realmente se trata de recuperación de datos, proyección, transformación, etc. La modificación de colecciones existentes realmente no entra en el ámbito del propósito previsto de LINQ, por lo que LINQ no proporciona nada más de la caja para esto. Pero los métodos de extensión (y en particular su muestra) serían ideales para esto.
Levi
Un problema, ICollection<T>no parece tener un Addmétodo. msdn.microsoft.com/en-us/library/… Sin embargo, Collection<T>tiene uno.
Tim Goodman
@TimGoodman: esa es la interfaz no genérica. Ver msdn.microsoft.com/en-us/library/92t2ye13.aspx
TrueWill
"La modificación de colecciones existentes realmente no entra en el ámbito del propósito previsto de LINQ". @Levi Entonces, ¿por qué tenerlo Add(T item)en primer lugar? Parece un enfoque a medias para ofrecer la capacidad de agregar un solo elemento y luego esperar que todas las personas que llaman repitan para agregar más de uno a la vez. Su declaración es ciertamente cierta para, IEnumerable<T>pero me he sentido frustrado ICollectionsen más de una ocasión. No estoy en desacuerdo contigo, solo desahogo.
Akousmata

Respuestas:

62

No, esto parece perfectamente razonable. Hay un método List<T>.AddRange () que básicamente hace esto, pero requiere que su colección sea concreta List<T>.

Reed Copsey
fuente
1
Gracias; muy cierto, pero la mayoría de las propiedades públicas siguen las pautas de MS y no son listas.
TrueWill
7
Sí, lo estaba dando más como justificación de por qué no creo que haya un problema en hacer esto. Solo tenga en cuenta que será menos eficiente que la versión List <T> (ya que la list <T> puede preasignar)
Reed Copsey
Solo tenga cuidado de que el método AddRange en .NET Core 2.2 podría mostrar un comportamiento extraño si se usa incorrectamente, como se muestra en este número: github.com/dotnet/core/issues/2667
Bruno
36

Intente transmitir a List en el método de extensión antes de ejecutar el bucle. De esa manera, puede aprovechar el rendimiento de List.AddRange.

public static void AddRange<T>(this ICollection<T> destination,
                               IEnumerable<T> source)
{
    List<T> list = destination as List<T>;

    if (list != null)
    {
        list.AddRange(source);
    }
    else
    {
        foreach (T item in source)
        {
            destination.Add(item);
        }
    }
}
rymdsmurf
fuente
2
El asoperador nunca lanzará. Si destinationno se puede lanzar, listserá nulo y el elsebloque se ejecutará.
rymdsmurf
4
arrgggh! ¡Cambie las ramas de condición, por el amor de todo lo que es santo!
nicodemus13
13
Lo digo en serio, la razón principal es que es una carga cognitiva adicional, que a menudo es bastante difícil. Estás constantemente tratando de evaluar las condiciones negativas, lo que generalmente es relativamente difícil, tienes ambas ramas de todos modos, es (IMO) más fácil decir 'si es nulo' haz esto, 'si no' haz esto, en lugar de lo contrario. También se trata de valores predeterminados, deben ser el concepto positivo tan a menudo como sea posible, por ejemplo, `if (! Thing.IsDisabled) {} ​​else {} 'requiere que te detengas y pienses' ah, no está deshabilitado significa que está habilitado, correcto, lo tengo, por lo que la otra rama es cuando está deshabilitada). Difícil de analizar.
nicodemus13
13
Interpretar "algo! = Nulo" no es más difícil que interpretar "algo == nulo". Sin embargo, el operador de negación es algo completamente diferente, y en su último ejemplo, reescribir la instrucción if-else eliminaría ese operador. Eso es una mejora objetivamente, pero que no está relacionada con la pregunta original. En ese caso particular, las dos formas son una cuestión de preferencias personales, y preferiría el operador "! =" -, dado el razonamiento anterior.
rymdsmurf
15
La coincidencia de patrones hará felices a todos ... ;-)if (destination is List<T> list)
Jacob Foshee
28

Ya que .NET4.5si quieres one-liner puedes usar System.Collections.GenericForEach.

source.ForEach(o => destination.Add(o));

o incluso más corto como

source.ForEach(destination.Add);

En cuanto al rendimiento, es el mismo que para cada bucle (azúcar sintáctico).

Además , no intentes asignarlo como

var x = source.ForEach(destination.Add) 

la causa ForEaches nula.

Editar: Copiado de los comentarios, la opinión de Lipert sobre ForEach

Matas Vaitkevicius
fuente
9
Personalmente, estoy con Lippert en este: blogs.msdn.com/b/ericlippert/archive/2009/05/18/…
TrueWill
1
¿Debería ser source.ForEach (destino.Añadir)?
Frank
4
ForEachparece que solo se define en List<T>, Collection¿ no ?
Protector uno
Lippert ahora se puede encontrar en web.archive.org/web/20190316010649/https://…
user7610
Enlace actualizado a la publicación del blog de Eric Lippert: Fabulous Adventures in Coding | "Foreach" vs "ForEach"
Alexander
19

Recuerda que cada uno Addcomprobará la capacidad de la colección y la redimensionará cuando sea necesario (más lento). Con AddRange, se configurará la capacidad de la colección y luego se agregarán los elementos (más rápido). Este método de extensión será extremadamente lento, pero funcionará.

jvitor83
fuente
3
Para agregar a esto, también habrá una notificación de cambio de colección para cada adición, a diferencia de una notificación masiva con AddRange.
Nick Udell
3

Aquí hay una versión un poco más avanzada / lista para producción:

    public static class CollectionExtensions
    {
        public static TCol AddRange<TCol, TItem>(this TCol destination, IEnumerable<TItem> source)
            where TCol : ICollection<TItem>
        {
            if(destination == null) throw new ArgumentNullException(nameof(destination));
            if(source == null) throw new ArgumentNullException(nameof(source));

            // don't cast to IList to prevent recursion
            if (destination is List<TItem> list)
            {
                list.AddRange(source);
                return destination;
            }

            foreach (var item in source)
            {
                destination.Add(item);
            }

            return destination;
        }
    }
MovGP0
fuente
La respuesta de rymdsmurf puede parecer ingenua, demasiado simple, pero funciona con listas heterogéneas. ¿Es posible hacer que este código admita este caso de uso?
richardsonwtr
Por ejemplo: destinationes una lista de Shape, una clase abstracta. sourcees una lista de Circleuna clase heredada.
richardsonwtr
1

Los C5 Colecciones Biblioteca genérica de clases todo el apoyo del AddRangemétodo. C5 tiene una interfaz mucho más robusta que realmente expone todas las características de sus implementaciones subyacentes y es compatible con las interfaces System.Collections.Generic ICollectiony IList, lo que significa que C5las colecciones se pueden sustituir fácilmente como la implementación subyacente.

Marcus Griep
fuente
0

Puede agregar su rango IEnumerable a una lista y luego establecer ICollection = en la lista.

        IEnumerable<T> source;

        List<item> list = new List<item>();
        list.AddRange(source);

        ICollection<item> destination = list;
Jonathan Jansen
fuente
3
Si bien esto funciona funcionalmente, rompe las pautas de Microsoft para hacer que las propiedades de la colección sean de solo lectura ( msdn.microsoft.com/en-us/library/ms182327.aspx )
Nick Udell
0

O simplemente puede hacer una extensión ICollection como esta:

 public static ICollection<T> AddRange<T>(this ICollection<T> @this, IEnumerable<T> items)
    {
        foreach(var item in items)
        {
            @this.Add(item);
        }

        return @this;
    }

Usarlo sería como usarlo en una lista:

collectionA.AddRange(IEnumerable<object> items);
Katarina Kelam
fuente