Array versus List <T>: ¿Cuándo usar cuál?

594
MyClass[] array;
List<MyClass> list;

¿Cuáles son los escenarios cuando uno es preferible al otro? ¿Y por qué?

Federico el tonto
fuente
99
Las matrices son bastante obsoletas, como se ve en una discusión popular aquí. También señaló aquí , y por nuestro anfitrión en el blog .
gimel
99
Si no me equivoco, la Lista <> tiene una matriz como estructura interna. Cada vez que se llena la matriz interna, simplemente copie el contenido en una matriz que sea el doble del tamaño (o alguna otra constante por el tamaño actual). en.wikipedia.org/wiki/Dynamic_array
Ykok
Ykok: Lo que dices parece correcto, encontré el código fuente de List <> aquí .
Carol
19
@gimel Argumentar que las matrices son obsoletas quizás sea un poco audaz
awdz9nld

Respuestas:

580

Es raro, en realidad, que desee utilizar una matriz. Definitivamente use un en List<T>cualquier momento que desee agregar / eliminar datos, ya que cambiar el tamaño de las matrices es costoso. Si sabe que los datos tienen una longitud fija y desea realizar una micro optimización por alguna razón muy específica (después de la evaluación comparativa), una matriz puede ser útil.

List<T>ofrece mucha más funcionalidad que una matriz (aunque LINQ lo iguala un poco), y casi siempre es la elección correcta. Excepto por paramsargumentos, por supuesto. ;-pags

Como contador, List<T>es unidimensional; donde-ya que tiene matrices rectangulares (etc.) como int[,]o string[,,]- pero hay otras formas de modelar dichos datos (si es necesario) en un modelo de objetos.

Ver también:

Dicho esto, uso mucho las matrices en mi proyecto protobuf-net ; enteramente para el rendimiento:

  • hace muchos cambios de bits, por lo que a byte[]es bastante esencial para la codificación;
  • Utilizo un byte[]búfer rodante local que lleno antes de enviarlo a la secuencia subyacente (y vv); más rápido que BufferedStreametc.
  • internamente usa un modelo de objetos basado en una matriz (en Foo[]lugar de List<Foo>), ya que el tamaño se fija una vez construido y debe ser muy rápido.

Pero esto es definitivamente una excepción; para el procesamiento general de la línea de negocio, un List<T>gana cada vez.

Marc Gravell
fuente
8
El argumento sobre el cambio de tamaño es totalmente válido. Sin embargo, las personas prefieren listas incluso cuando no se necesita cambiar el tamaño. Para este último caso, ¿existe un argumento sólido y lógico o no es más que "las matrices están pasadas de moda"?
Frederick The Fool
66
"Definitivamente use una Lista <T> cada vez que desee agregar / eliminar datos, ya que cambiar el tamaño de las matrices es costoso". La lista <T> usa una matriz internamente. ¿Estabas pensando en LinkedList <T>?
dan-gph
14
Más funciones == más complejas == no son buenas, a menos que necesite esas funciones. Esta respuesta básicamente enumera las razones por las cuales las matrices son mejores, pero saca la conclusión opuesta.
Eamon Nerbonne
12
@EamonNerbonne si no estás usando esas funciones, puedo garantizar que no te harán daño ... pero: la cantidad de colecciones que nunca necesitan mutación es mucho menor, en mi experiencia, que las que son mutado
Marc Gravell
77
@MarcGravell: eso depende de tu estilo de codificación. En mi experiencia, prácticamente no hay colecciones mutadas. Es decir; Las colecciones se recuperan de la base de datos o se construyen a partir de alguna fuente, pero el procesamiento posterior siempre se realiza recreando una nueva colección (por ejemplo, mapa / filtro, etc.). Incluso cuando la mutación conceptual es necesaria, tiende a ser más simple generar una nueva colección. Solo muto una colección como una optimización del rendimiento, y tales optimizaciones tienden a ser muy locales y no exponen la mutación a los consumidores de API.
Eamon Nerbonne
121

Realmente solo respondo para agregar un enlace que me sorprende que aún no se haya mencionado: la entrada del blog de Eric Lippert sobre "Matrices consideradas algo dañinas".

Puede juzgar por el título que sugiere usar colecciones siempre que sea práctico, pero como Marc señala con razón, hay muchos lugares donde una matriz realmente es la única solución práctica.

Jon Skeet
fuente
2
Finalmente pude leer esto más de 3 años después, jaja. Buen artículo entonces, buen artículo ahora. :)
Spencer Ruport
21

A pesar de las otras respuestas recomendadas List<T>, querrá usar matrices cuando maneje:

  • datos de mapa de bits de imagen
  • otras estructuras de datos de bajo nivel (es decir, protocolos de red)
Alnitak
fuente
1
¿Por qué para los protocolos de red? ¿No preferirías usar estructuras personalizadas aquí y darles un serializador especial o un diseño de memoria explícito? Además, ¿qué habla en contra de usar un List<T>conjunto de bytes aquí en lugar de uno?
Konrad Rudolph el
8
@Konrad: bueno, para empezar, Stream.Read y Stream.Write funcionan con el byte [], al igual que Encoding, etc ...
Marc Gravell
12

A menos que esté realmente preocupado por el rendimiento, y con eso quiero decir, "¿Por qué estás usando .Net en lugar de C ++?" deberías seguir con la Lista <>. Es más fácil de mantener y hace todo el trabajo sucio de cambiar el tamaño de una matriz detrás de escena por usted. (Si es necesario, List <> es bastante inteligente sobre la elección de tamaños de matriz, por lo que generalmente no es necesario).

Spencer Ruport
fuente
15
"¿Por qué estás usando .Net en lugar de C ++?" XNA
Bengt
6

Las matrices deben usarse con preferencia a la Lista cuando la inmutabilidad de la colección en sí misma es parte del contrato entre el código del cliente y el proveedor (no necesariamente la inmutabilidad de los artículos dentro de la colección) Y cuando IEnumerable no es adecuado.

Por ejemplo,

var str = "This is a string";
var strChars = str.ToCharArray();  // returns array

Está claro que la modificación de "strChars" no mutará el objeto "str" ​​original, independientemente del nivel de implementación del tipo subyacente de "str".

Pero supongamos que

var str = "This is a string";
var strChars = str.ToCharList();  // returns List<char>
strChars.Insert(0, 'X');

En este caso, no está claro por ese fragmento de código solo si el método de inserción mutará o no el objeto "str" ​​original. Se requiere conocimiento de nivel de implementación de String para tomar esa determinación, lo que rompe el enfoque de Diseño por Contrato. En el caso de String, no es un gran problema, pero puede ser un gran problema en casi todos los demás casos. Establecer la Lista en solo lectura ayuda pero resulta en errores de tiempo de ejecución, no en tiempo de compilación.

Herman Schoenfeld
fuente
Soy relativamente nuevo en C #, pero no me queda claro por qué devolver una lista sugeriría la mutabilidad de los datos originales de una manera que no devolvería una matriz. Sin embargo, habría pensado que un método cuyo nombre comience Tova a crear un objeto que no tiene capacidad para modificar la instancia original, a diferencia de lo strChars as char[]que si es válido, sugeriría que ahora puede modificar el objeto original.
Tim MB
@TimMB Existe la inmutabilidad de la colección (no se pueden agregar o elementos remotos) y la inmutabilidad de los elementos en la colección. Me refería a esto último, mientras que probablemente estás combinando los dos. Devolver una matriz asegura al cliente que no puede agregar / eliminar elementos. Si lo hace, reasigna la matriz y se asegura de que no afectará al original. Al devolver una lista, no se realizan tales garantías y el original podría verse afectado (depende de la implementación). Cambiar elementos dentro de la colección (ya sea matriz o lista) podría afectar el original, si el tipo de elemento no es una estructura.
Herman Schoenfeld
Gracias por la aclaración. Todavía estoy confundido (probablemente porque vengo del mundo de C ++). Si strutiliza internamente una matriz y ToCharArraydevuelve una referencia a esta matriz, el cliente puede mutar strcambiando los elementos de esa matriz, incluso si el tamaño permanece fijo. Sin embargo, escribe 'Está claro que la modificación de "strChars" no mutará el objeto "str" ​​original'. ¿Que me estoy perdiendo aqui? Por lo que puedo ver, en cualquier caso, el cliente puede tener acceso a la representación interna y, independientemente del tipo, esto permitiría la mutación de algún tipo.
Tim MB
3

Si sé exactamente cuántos elementos que voy a necesidad, decir que necesito 5 elementos y sólo alguna vez 5 elementos, entonces yo uso una matriz. De lo contrario, solo uso una Lista <T>.

smack0007
fuente
1
¿Por qué no usaría una Lista <T> en el caso de que conozca el número de elementos?
Oliver
3

La mayoría de las veces, usar un Listsería suficiente. A Listusa una matriz interna para manejar sus datos, y automáticamente cambia el tamaño de la matriz al agregar más elementos a Listsu capacidad actual, lo que hace que sea más fácil de usar que una matriz, donde necesita conocer la capacidad de antemano.

Consulte http://msdn.microsoft.com/en-us/library/ms379570(v=vs.80).aspx#datastructures20_1_topic5 para obtener más información sobre las listas en C # o simplemente descompilar System.Collections.Generic.List<T>.

Si necesita datos multidimensionales (por ejemplo, utilizando una matriz o en la programación de gráficos), probablemente iría con un arrayen su lugar.

Como siempre, si la memoria o el rendimiento son un problema, ¡mídelo! De lo contrario, podría estar haciendo suposiciones falsas sobre el código.

Sune Rievers
fuente
1
Hola, ¿podría explicar por qué "El tiempo de búsqueda de una lista sería O (n)" es cierto? Hasta donde yo sé, la Lista <T> usa una matriz detrás de escena.
libélula
1
@dragonfly tienes toda la razón. Fuente . En ese momento, supuse que la implementación usaba punteros, pero desde entonces he aprendido lo contrario. Desde el enlace anterior: 'Recuperar el valor de esta propiedad es una operación O (1); establecer la propiedad también es una operación O (1) '.
Sune Rievers
2

Arreglos vs. Listas es un problema clásico de mantenimiento versus rendimiento. La regla general que siguen casi todos los desarrolladores es que debes disparar para ambos, pero cuando entren en conflicto, elige la mantenibilidad sobre el rendimiento. La excepción a esa regla es cuando el rendimiento ya ha demostrado ser un problema. Si llevas este principio a Arrays Vs. Listas, entonces lo que obtienes es esto:

Use listas fuertemente tipadas hasta que encuentre problemas de rendimiento. Si se encuentra con un problema de rendimiento, decida si abandonar los arreglos beneficiará más su solución con el rendimiento que lo que será perjudicial para su solución en términos de mantenimiento.

Melbourne Developer
fuente
1

Otra situación aún no mencionada es cuando uno tendrá una gran cantidad de elementos, cada uno de los cuales consiste en un grupo fijo de variables relacionadas pero independientes unidas (por ejemplo, las coordenadas de un punto o los vértices de un triángulo 3d). Una matriz de estructuras de campo expuesto permitirá que sus elementos se modifiquen eficientemente "en su lugar", algo que no es posible con ningún otro tipo de colección. Debido a que una matriz de estructuras mantiene sus elementos consecutivamente en la RAM, los accesos secuenciales a los elementos de la matriz pueden ser muy rápidos. En situaciones donde el código necesitará hacer muchos pases secuenciales a través de una matriz, una matriz de estructuras puede superar a una matriz u otra colección de referencias de objetos de clase por un factor de 2: 1; más lejos,

Aunque las matrices no son redimensionables, no es difícil que el código almacene una referencia de matriz junto con el número de elementos que están en uso, y reemplace la matriz por una más grande según sea necesario. Alternativamente, uno podría escribir fácilmente código para un tipo que se comportó de manera muy similar a un List<T>pero expuso su almacén de respaldo, lo que le permite a uno decir MyPoints.Add(nextPoint);o MyPoints.Items[23].X += 5;. Tenga en cuenta que este último no necesariamente arrojaría una excepción si el código intentara acceder más allá del final de la lista, pero el uso sería conceptualmente bastante similar a List<T>.

Super gato
fuente
Lo que describiste es una Lista <>. Hay un indexador para que pueda acceder directamente a la matriz subyacente, y la Lista <> mantendrá el tamaño por usted.
Carl
@Carl: Dado Point[] arr;, por ejemplo , es posible que el código diga, por ejemplo arr[3].x+=q;. Usando List<Point> list, por ejemplo , sería necesario decirPoint temp=list[3]; temp.x+=q; list[3]=temp; . Sería útil si List<T>tuviera un método Update<TP>(int index, ActionByRefRef<T,TP> proc, ref TP params). y los compiladores podrían convertir list[3].x+=q;en {list.Update(3, (ref int value, ref int param)=>value+=param, ref q);, pero no existe tal función.
supercat
Buenas noticias. Funciona. list[0].X += 3;agregará 3 a la propiedad X del primer elemento de la lista. Y listes un List<Point>y Pointes una clase con propiedades X e Y
Carl
1

Las listas en .NET son envoltorios sobre matrices y usan una matriz internamente. La complejidad temporal de las operaciones en las listas es la misma que con las matrices, sin embargo, hay un poco más de sobrecarga con toda la funcionalidad agregada / facilidad de uso de las listas (como el cambio de tamaño automático y los métodos que vienen con la clase de lista). Más o menos, recomendaría usar listas en todos los casos a menos que haya una razón convincente para no hacerlo, como si necesita escribir código extremadamente optimizado o si está trabajando con otro código que se construye alrededor de matrices.

iliketocode
fuente
0

En lugar de pasar por una comparación de las características de cada tipo de datos, creo que la respuesta más pragmática es "las diferencias probablemente no sean tan importantes para lo que necesita lograr, especialmente dado que ambas implementan IEnumerable, así que siga las convenciones populares y use un Listhasta que tenga una razón para no hacerlo, momento en el cual probablemente tendrá su razón para usar una matriz sobre un List".

La mayoría de las veces en código administrado querrá favorecer que las colecciones sean tan fáciles de trabajar como sea posible en lugar de preocuparse por las micro optimizaciones.

placa de moarboiler
fuente
0

Pueden ser impopulares, pero soy fanático de las matrices en proyectos de juegos. - La velocidad de iteración puede ser importante en algunos casos, foreach en una matriz tiene significativamente menos sobrecarga si no está haciendo mucho por elemento. puede que no importe: en la mayoría de los casos, se desperdicia menos memoria adicional (solo es realmente significativo con matrices de estructuras): un poco menos de basura, punteros y persecución de punteros

Dicho esto, en la práctica uso List con mucha más frecuencia que las matrices, pero cada una tiene su lugar.

Sería bueno si List tuviera un tipo incorporado para que pudieran optimizar la envoltura y la sobrecarga de enumeración.


fuente
0

Completar una lista es más fácil que una matriz. Para las matrices, debe conocer la longitud exacta de los datos, pero para las listas, el tamaño de los datos puede ser cualquiera. Y, puede convertir una lista en una matriz.

List<URLDTO> urls = new List<URLDTO>();

urls.Add(new URLDTO() {
    key = "wiki",
    url = "https://...",
});

urls.Add(new URLDTO()
{
    key = "url",
    url = "http://...",
});

urls.Add(new URLDTO()
{
    key = "dir",
    url = "https://...",
});

// convert a list into an array: URLDTO[]
return urls.ToArray();
Poudel Bimal
fuente
0

Como nadie menciona: en C #, una matriz es una lista. MyClass[]y List<MyClass>ambos implementan IList<MyClass>. (por ejemplo, void Foo(IList<int> foo)se puede llamar como Foo(new[] { 1, 2, 3 })o Foo(new List<int> { 1, 2, 3 }))

Por lo tanto, si está escribiendo un método que acepta a List<MyClass>como argumento, pero usa solo un subconjunto de características, puede declarar comoIList<MyClass> para conveniencia de las personas que llaman.

Detalles:

snipsnipsnip
fuente
"En C #, una matriz es una lista" Eso no es cierto; una matriz no es una List, solo implementa la IListinterfaz.
Rufus L
-1

Depende completamente de los contextos en los que se necesita la estructura de datos. Por ejemplo, si está creando elementos para ser utilizados por otras funciones o servicios, usar List es la manera perfecta de lograrlo.

Ahora, si tiene una lista de elementos y solo desea mostrarlos, digamos que en una matriz de páginas web se encuentra el contenedor que necesita usar.

sajidnizami
fuente
1
Si tiene una lista de elementos y solo desea mostrarlos, ¿qué tiene de malo usar la lista que ya tiene? ¿Qué ofrecería una matriz aquí?
Marc Gravell
1
Y para "crear elementos para ser utilizados por otras funciones o servicios", en realidad, preferiría un bloque iterador con IEnumerable<T>- luego puedo transmitir objetos en lugar de almacenarlos en búfer.
Marc Gravell
-1

Vale la pena mencionar sobre la capacidad de hacer casting en el lugar.

interface IWork { }
class Foo : IWork { }

void Test( )
{
    List<Foo> bb = new List<Foo>( );
    // Error: CS0029 Cannot implicitly convert type 'System.Collections.Generic.List<Foo>' to 'System.Collections.Generic.List<IWork>'
    List<IWork> cc = bb; 

    Foo[] bbb = new Foo[4];
    // Fine
    IWork[] ccc = bbb;
}

Entonces Array ofrece un poco más de flexibilidad cuando se usa en el tipo de retorno o argumento para funciones.

IWork[] GetAllWorks( )
{
    List<Foo> fooWorks = new List<Foo>( );
    return fooWorks.ToArray( ); // Fine
}

void ExecuteWorks( IWork[] works ) { } // Also accept Foo[]
Wappenull
fuente
Debe tener en cuenta que la variación de la matriz está rota .
mcarton