¿Cómo inicializar una List <T> a un tamaño determinado (en oposición a la capacidad)?

111

.NET ofrece un contenedor de lista genérico cuyo rendimiento es casi idéntico (consulte la pregunta Rendimiento de matrices frente a listas). Sin embargo, son bastante diferentes en la inicialización.

Las matrices son muy fáciles de inicializar con un valor predeterminado y, por definición, ya tienen cierto tamaño:

string[] Ar = new string[10];

Lo que permite a uno asignar de forma segura elementos aleatorios, digamos:

Ar[5]="hello";

con la lista, las cosas son más complicadas. Puedo ver dos formas de hacer la misma inicialización, ninguna de las cuales es lo que llamarías elegante:

List<string> L = new List<string>(10);
for (int i=0;i<10;i++) L.Add(null);

o

string[] Ar = new string[10];
List<string> L = new List<string>(Ar);

¿Cuál sería una forma más limpia?

EDITAR: Las respuestas hasta ahora se refieren a la capacidad, que es algo más que rellenar previamente una lista. Por ejemplo, en una lista recién creada con una capacidad de 10, no se puede hacerL[2]="somevalue"

EDICIÓN 2: La gente se pregunta por qué quiero usar las listas de esta manera, ya que no es la forma en que deben usarse. Puedo ver dos razones:

  1. Se podría argumentar de manera bastante convincente que las listas son los arreglos de la "próxima generación", lo que agrega flexibilidad sin casi ninguna penalización. Por lo tanto, uno debería usarlos por defecto. Estoy señalando que es posible que no sean tan fáciles de inicializar.

  2. Lo que estoy escribiendo actualmente es una clase base que ofrece una funcionalidad predeterminada como parte de un marco más grande. En la funcionalidad predeterminada que ofrezco, el tamaño de la Lista se conoce de manera avanzada y, por lo tanto, podría haber usado una matriz. Sin embargo, quiero ofrecer a cualquier clase base la posibilidad de ampliarla dinámicamente y, por tanto, opto por una lista.

Booz
fuente
1
"EDITAR: Las respuestas hasta ahora se refieren a la capacidad, que es algo más que rellenar previamente una lista. Por ejemplo, en una lista recién creada con una capacidad 10, no se puede hacer L [2] =" algún valor "" Dado esto modificación, tal vez debería reformular el título de la pregunta ...
aranasaurus
Pero, ¿de qué sirve rellenar previamente una lista con valores vacíos, porque eso es lo que intenta hacer el iniciador de temas?
Frederik Gheysels
1
Si el mapeo posicional es tan crucial, ¿no tendría más sentido usar un Dictionary <int, string>?
Greg D
7
Listno es un reemplazo para Array. Resuelven problemas claramente separados. Si quieres un tamaño fijo, quieres un Array. Si usa un List, lo está haciendo mal.
Zenexer
5
Siempre encuentro respuestas que intentan martillar argumentos como "No veo por qué necesitaría ..." agravantes. Solo significa eso: no podías verlo. No significa necesariamente nada más. Respeto que la gente quiera sugerir mejores enfoques para un problema, pero debería expresarse de manera más humilde, por ejemplo, "¿Estás seguro de que necesitas una lista? Quizás si nos dijeras más sobre tu problema ...". De esta manera, se vuelve agradable, atractivo y anima al OP a mejorar su pregunta. Sea un ganador, sea humilde.
AnorZaken

Respuestas:

79

No puedo decir que necesite esto muy a menudo, ¿podría dar más detalles sobre por qué quiere esto? Probablemente lo pondría como un método estático en una clase auxiliar:

public static class Lists
{
    public static List<T> RepeatedDefault<T>(int count)
    {
        return Repeated(default(T), count);
    }

    public static List<T> Repeated<T>(T value, int count)
    {
        List<T> ret = new List<T>(count);
        ret.AddRange(Enumerable.Repeat(value, count));
        return ret;
    }
}

Usted podría utilizar Enumerable.Repeat(default(T), count).ToList(), pero que podría ser debido ineficaz para amortiguar el cambio de tamaño.

Tenga en cuenta que si Tes un tipo de referencia, almacenará countcopias de la referencia pasada para el valueparámetro, por lo que todas harán referencia al mismo objeto. Eso puede ser o no lo que desea, dependiendo de su caso de uso.

EDITAR: Como se señaló en los comentarios, puede Repeatedusar un bucle para completar la lista si lo desea. Eso también sería un poco más rápido. Personalmente, el código me parece Repeatmás descriptivo y sospecho que en el mundo real la diferencia de rendimiento sería irrelevante, pero su kilometraje puede variar.

Jon Skeet
fuente
1
Me doy cuenta de que esta es una publicación antigua, pero tengo curiosidad. Enumerable Las tarifas de repetición son mucho peores en comparación con un bucle for, según la última parte del enlace ( dotnetperls.com/initialize-array ). Además, AddRange () tiene complejidad O (n) según msdn. ¿No es un poco contraproducente usar la solución dada en lugar de un simple bucle?
Jimmy
@Jimmy: Ambos enfoques serán O (n), y encuentro que este enfoque es más descriptivo de lo que estoy tratando de lograr. Si prefiere un bucle, no dude en usarlo.
Jon Skeet
@Jimmy: Tenga en cuenta también que el punto de referencia se está usando Enumerable.Repeat(...).ToArray(), que no es como lo estoy usando.
Jon Skeet
1
He usado el Enumerable.Repeat()de la siguiente manera (implementado Paircomo el equivalente de C ++): Enumerable.Repeat( new Pair<int,int>(int.MaxValue,-1), costs.Count)notando el efecto secundario, que Listestaba lleno de copias referenciadas a un solo objeto. Cambiar un elemento como myList[i].First = 1cambió cada elemento del todo List. Me tomó horas encontrar este error. ¿Conocen alguna solución a este problema (excepto por el uso de un bucle común y el uso .Add(new Pair...)?)
00zetti
1
@ Pac0, acabo de agregar una respuesta a continuación. Las ediciones se pueden rechazar.
Jeremy Ray Brown
136
List<string> L = new List<string> ( new string[10] );

fuente
3
personalmente, creo que esta es la forma más limpia, aunque se mencionó en el cuerpo de la pregunta como potencialmente torpe, no sé por qué
hello_earth
4
+1: Acabo de llegar a una situación en la que necesitaba que se inicializara una lista de tamaño variable con un conjunto fijo de nulos antes de completar a través de un índice (y agregar extras después, por lo que una matriz no era adecuada). Esta respuesta recibe mi voto por ser práctica y sencilla.
Gone Coding
Respuesta fabulosa. @GoneCoding Me preguntaba si internamente la lista recién inicializada Lsimplemente reutilizará la memoria string[10]tal como está (el almacenamiento de respaldo de una lista también es una matriz) o asignará una nueva memoria propia y luego copiará el contenido de string[10]? string[10]obtendrá la basura recolectada automáticamente si Lselecciona la ruta posterior.
RBT
3
@RBT Esa es la única desventaja de este enfoque: la matriz no se reutilizará, por lo que momentáneamente tendrá dos copias de la matriz (List usa matrices internamente, como parece que sabe). En casos excepcionales, eso podría ser un problema si tiene una gran cantidad de elementos o limitaciones de memoria. Y sí, la matriz adicional será elegible para la recolección de basura tan pronto como el constructor de List esté listo (de lo contrario, habría sido una pérdida de memoria horrible). Tenga en cuenta que elegible no significa "recolectado de inmediato", sino la próxima vez que se ejecute la recolección de basura.
AnorZaken
2
Esta respuesta asigna 10 cadenas nuevas y luego las repite y las copia según sea necesario. Si está trabajando con matrices grandes; entonces ni siquiera considere esto, ya que necesita el doble de memoria que la respuesta aceptada.
UKMonkey
22

Utilice el constructor que toma un int ("capacidad") como argumento:

List<string> = new List<string>(10);

EDITAR: Debo agregar que estoy de acuerdo con Frederik. Está usando la Lista de una manera que va en contra de todo el razonamiento detrás de su uso en primer lugar.

EDIT2:

EDITAR 2: Lo que estoy escribiendo actualmente es una clase base que ofrece una funcionalidad predeterminada como parte de un marco más grande. En la funcionalidad predeterminada que ofrezco, el tamaño de la Lista se conoce de manera avanzada y, por lo tanto, podría haber usado una matriz. Sin embargo, quiero ofrecer a cualquier clase base la posibilidad de ampliarla dinámicamente y, por tanto, opto por una lista.

¿Por qué alguien necesitaría saber el tamaño de una lista con todos los valores nulos? Si no hay valores reales en la lista, esperaría que la longitud sea 0. De todos modos, el hecho de que esto sea complicado demuestra que va en contra del uso previsto de la clase.

Ed S.
fuente
46
Esta respuesta no asigna 10 entradas nulas en la lista (que era el requisito), simplemente asigna espacio para 10 entradas antes de que se requiera un cambio de tamaño de la lista (es decir, capacidad), por lo que esto no hace nada diferente en lo new List<string>()que respecta al problema. . Bien hecho por conseguir tantos votos positivos :)
Gone Coding
2
ese constructor sobrecargado es el valor de "capacidad inicial", no el "tamaño" o la "longitud", y tampoco inicializa los elementos
Matt Wilko
Para responder "¿por qué alguien necesitaría esto?": Lo necesito ahora mismo para estructuras de datos de árboles de clonación profunda. Un nodo puede o no poblar sus hijos, pero mi clase de nodo base necesita poder clonarse con todos sus nodos secundarios. Bla, bla, es aún más complicado. Pero necesito completar mi lista todavía vacía a través de list[index] = obj;y usar algunas otras capacidades de lista.
Bitterblue
3
@Bitterblue: Además, cualquier tipo de mapeo preasignado donde es posible que no tenga todos los valores por adelantado. Ciertamente hay usos; Escribí esta respuesta en mis días con menos experiencia.
Ed S.
8

Cree una matriz con la cantidad de elementos que desee primero y luego convierta la matriz en una Lista.

int[] fakeArray = new int[10];

List<int> list = fakeArray.ToList();
mini998
fuente
7

¿Por qué utiliza una lista si desea inicializarla con un valor fijo? Puedo entender que, por el bien del rendimiento, desea darle una capacidad inicial, pero ¿no es una de las ventajas de una lista sobre una matriz regular que puede crecer cuando sea necesario?

Cuando haces esto:

List<int> = new List<int>(100);

Creas una lista cuya capacidad es de 100 enteros. Esto significa que su Lista no necesitará "crecer" hasta que agregue el elemento 101. La matriz subyacente de la lista se inicializará con una longitud de 100.

Frederik Gheysels
fuente
1
"¿Por qué estás usando una lista si quieres inicializarla con un valor fijo?" Un buen punto.
Ed S.
7
Pidió una lista en la que se inicializara cada elemento y la lista tuviera un tamaño, no solo una capacidad. Esta respuesta es incorrecta tal como está ahora.
Nic Foster
La pregunta requiere una respuesta, no una crítica. A veces hay una razón para hacerlo de esta manera, en lugar de usar una matriz. Si está interesado en saber por qué este podría ser el caso, puede hacer una pregunta de Stack Overflow.
Dino Dini
5

Inicializar el contenido de una lista como esa no es realmente para lo que sirven las listas. Las listas están diseñadas para contener objetos. Si desea asignar números particulares a objetos particulares, considere usar una estructura de par clave-valor como una tabla hash o un diccionario en lugar de una lista.

Welbog
fuente
Esto no responde a la pregunta, sino que simplemente da una razón para que no se haga.
Dino Dini
5

Parece estar enfatizando la necesidad de una asociación posicional con sus datos, entonces, ¿no sería más apropiado un arreglo asociativo?

Dictionary<int, string> foo = new Dictionary<int, string>();
foo[2] = "string";
Greg D
fuente
Esto responde a una pregunta diferente a la que se está formulando.
Dino Dini
4

Si desea inicializar la lista con N elementos de algún valor fijo:

public List<T> InitList<T>(int count, T initValue)
{
  return Enumerable.Repeat(initValue, count).ToList();
}
Amy B
fuente
Consulte la respuesta de John Skeet sobre la preocupación por el cambio de tamaño del búfer mediante esta técnica.
Amy B
1

Puede usar Linq para inicializar inteligentemente su lista con un valor predeterminado. (Similar a la respuesta de David B. )

var defaultStrings = (new int[10]).Select(x => "my value").ToList();

Vaya un paso más allá e inicialice cada cadena con valores distintos "cadena 1", "cadena 2", "cadena 3", etc.

int x = 1;
var numberedStrings = (new int[10]).Select(x => "string " + x++).ToList();
James Lawruk
fuente
1
string [] temp = new string[] {"1","2","3"};
List<string> temp2 = temp.ToList();
Henk
fuente
¿Qué tal List <string> temp2 = new List <string> (temp); Como ya sugirió el OP.
Ed S.
Ed, este realmente responde a la pregunta, que no se trata de capacidad.
Boaz
Pero el OP ya declaró que no le gustaba esta solución.
Ed S.
1

La respuesta aceptada (la que tiene la marca de verificación verde) tiene un problema.

El problema:

var result = Lists.Repeated(new MyType(), sizeOfList);
// each item in the list references the same MyType() object
// if you edit item 1 in the list, you are also editing item 2 in the list

Recomiendo cambiar la línea de arriba para realizar una copia del objeto. Hay muchos artículos diferentes sobre eso:

Si desea inicializar todos los elementos de su lista con el constructor predeterminado, en lugar de NULL, agregue el siguiente método:

public static List<T> RepeatedDefaultInstance<T>(int count)
    {
        List<T> ret = new List<T>(count);
        for (var i = 0; i < count; i++)
        {
            ret.Add((T)Activator.CreateInstance(typeof(T)));
        }
        return ret;
    }
Jeremy Ray Brown
fuente
1
No es un "problema", es el comportamiento normal de copiar referencias. Sin conocer la situación, no puede saber si es adecuado copiar el objeto. La pregunta no es si las listas deben o no rellenarse con copias, se trata de inicializar una lista con una capacidad. Voy a aclarar mi respuesta en términos del comportamiento que no tiene, pero realmente no creo que se considera como un problema en el contexto de esta pregunta.
Jon Skeet
@JonSkeet, estaba respondiendo al ayudante estático, que está bien para tipos primitivos pero no para tipos de referencia. Un escenario en el que se inicializa una lista con el mismo elemento referenciado no parece correcto. En ese escenario, ¿por qué incluso tener la lista si cada elemento apunta al mismo objeto en el montón?
Jeremy Ray Brown
Escenario de muestra: desea completar una lista de cadenas, inicialmente con una entrada de "Desconocido" para cada elemento, luego modifica la lista para elementos específicos. En ese caso, es completamente razonable que todos los valores "Desconocidos" sean referencias a la misma cadena. No tendría sentido clonar la cadena cada vez. Completar una cadena con múltiples referencias al mismo objeto no es "correcto" o "incorrecto" en un sentido general. Siempre que los lectores sepan cuál es el comportamiento, depende de ellos decidir si cumple con su caso de uso específico .
Jon Skeet
0

Un aviso sobre IList: MSDN IList Comentarios : "Las implementaciones de IList se dividen en tres categorías: solo lectura, tamaño fijo y tamaño variable. (...). Para obtener la versión genérica de esta interfaz, consulte System.Collections.Generic.IList<T>."

IList<T>NO hereda de IList(pero List<T>implementa ambos IList<T>y IList), pero siempre es de tamaño variable . Desde .NET 4.5, también tenemos, IReadOnlyList<T>pero AFAIK, no hay una Lista genérica de tamaño fijo que sea lo que está buscando.

EricBDev
fuente
0

Esta es una muestra que utilicé para mi prueba unitaria. Creé una lista de objetos de clase. Luego usé forloop para agregar una cantidad 'X' de objetos que espero del servicio. De esta forma, puede agregar / inicializar una lista para cualquier tamaño dado.

public void TestMethod1()
    {
        var expected = new List<DotaViewer.Interface.DotaHero>();
        for (int i = 0; i < 22; i++)//You add empty initialization here
        {
            var temp = new DotaViewer.Interface.DotaHero();
            expected.Add(temp);
        }
        var nw = new DotaHeroCsvService();
        var items = nw.GetHero();

        CollectionAssert.AreEqual(expected,items);


    }

Espero haber sido de ayuda para ustedes.

Abhay Shiro
fuente
-1

Un poco tarde, pero la primera solución que propuso me parece mucho más limpia: no asigna memoria dos veces. Incluso el constrcutor de List necesita recorrer la matriz para copiarla; ni siquiera sabe de antemano que solo hay elementos nulos en su interior.

1. - asignar N - loop N Costo: 1 * asignar (N) + N * loop_iteration

2. - asignar N - asignar N + bucle () Costo: 2 * asignar (N) + N * loop_iteration

Sin embargo, la asignación de bucles de List puede ser más rápida ya que List es una clase incorporada, pero C # está compilado con jit tan ...

Victor Drouin
fuente