1. Introducción
Imagina que llevas una lista de la compra. Hoy necesitas comprar 3 cosas, mañana — 10, y pasado mañana — solo una. Si usaras un array, tendrías que crear uno nuevo cada vez para la cantidad exacta de productos. O crear un array enorme "por si acaso", llevando un montón de huecos vacíos. Suena poco eficiente, ¿verdad?
Ahora imagina que tienes una mochila mágica que se expande sola cuando metes cosas y se encoge cuando las sacas. No necesitas saber de antemano cuántas cosas vas a meter. Simplemente las metes y la mochila se adapta. ¡Eso es justo lo que hace List<T> en el mundo de la programación en C#!
List<T> es una colección genérica que te permite guardar una secuencia de elementos de un tipo concreto. La palabra clave aquí es "dinámica". A diferencia de los arrays, List<T> puede cambiar su tamaño durante la ejecución del programa. Se encarga de su capacidad interna, aumentándola cuando hace falta.
¿Qué significa la letra T en List<T>? Es lo que se llama un parámetro de tipo. Cuando creas un List, indicas qué tipo de datos va a guardar en vez de T. Por ejemplo, List<int> solo guardará números enteros, List<string> — solo cadenas, y List<double> — solo números decimales. Así te aseguras de que no vas a meter una manzana en una lista de naranjas, lo cual es clave para la estabilidad y previsibilidad de tu código.
¿Para qué sirve esto?
En proyectos reales es muy común no saber de antemano cuántos elementos vas a tener. Por ejemplo:
- Lista de usuarios que han entrado en la web.
- Lista de productos en el carrito de una tienda online.
- Lista de resultados de una búsqueda.
- Lista de tareas en un planificador, que es justo lo que vamos a desarrollar ahora.
En todos estos casos la cantidad de elementos puede cambiar, y List<T> se convierte en nuestro mejor aliado.
Métodos principales de List<T>
| Método | Descripción | Ejemplo |
|---|---|---|
|
Añadir un elemento al final de la lista | |
|
Insertar en una posición concreta | |
|
Eliminar la primera aparición de un elemento | |
|
Eliminar un elemento por índice | |
|
Vaciar la lista | |
|
Comprobar si la lista contiene un elemento | |
|
Índice de la primera aparición | |
|
Cantidad de elementos en la lista | |
|
Capacidad del buffer interno | |
2. Crear una lista (List<T>)
¡Vamos a la práctica! Imagina que queremos crear un gestor de tareas sencillo. Necesitamos una lista donde vamos a añadir nuestras tareas.
Para crear una nueva lista List<T> usamos el operador new, igual que al crear otros objetos.
Cómo declarar y crear un List
La sintaxis para crear una lista es muy parecida a la de los arrays, solo que en vez de corchetes usamos los signos < >:
using System.Collections.Generic;
List<int> numbers = new List<int>();
Comentarios para los que quieren detalles:
- List<int> es una lista de enteros (int). En vez de int puedes poner cualquier otro tipo: string, double, tu propia clase, etc.
- Al crearla, la lista está vacía — su longitud es 0.
Ejemplo: primer paso en nuestra app
Supón que tienes una app donde el usuario introducía nombre y edad (de las primeras lecciones). Ahora vamos a añadir la opción de guardar varios nombres en una lista para luego trabajar con ellos.
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> names = new List<string>();
Console.WriteLine("Introduce tres nombres:");
for (int i = 0; i < 3; i++)
{
string name = Console.ReadLine();
names.Add(name);
}
Console.WriteLine("Has introducido:");
foreach (string n in names)
{
Console.WriteLine(n);
}
}
}
Fíjate en Add(). Es este método el que añade nuevos elementos a la lista. ¡Eso es lo que la hace dinámica!
¡Puedes crear una lista ya con elementos!
List<string> planets = new List<string> { "Mercurio", "Venus", "Tierra" };
Muy cómodo cuando ya sabes el "equipo inicial". Esta sintaxis se usa mucho en tests y para inicializar datos fijos.
3. Operaciones básicas con List<T>
Antes de pasar a los trucos y la magia, vamos a ver el "abecedario" de las listas.
Añadir elementos: Add()
Usar el método Add() es como poner una cosa nueva en la estantería:
List<int> numbers = new List<int>();
numbers.Add(42);
numbers.Add(100);
Ahora dentro: [42, 100]
Obtener la cantidad de elementos: Count
A diferencia de los arrays, en las listas la propiedad se llama Count, no Length:
int count = numbers.Count; // Devuelve 2
Recuerda: en listas se usa Count, en arrays y strings — Length. Son propiedades distintas para un propósito parecido.
Acceso a elementos por índice
Dos formas, ambas hacen lo mismo:
int first = numbers[0]; // 42
numbers[1] = 200; // Cambiamos 100 por 200
Importante: La indexación, igual que en los arrays, empieza en cero. ¡No intentes acceder a numbers[3] si solo tienes tres elementos!
Recorrer los elementos de la lista
Puedes usar un bucle for o foreach:
// Con for — si necesitas el índice
for (int i = 0; i < numbers.Count; i++)
{
Console.WriteLine(numbers[i]);
}
// Con foreach — si no necesitas el índice
foreach (int number in numbers)
{
Console.WriteLine(number);
}
foreach suele ser más fácil y claro, sobre todo con colecciones grandes.
Pero con él no puedes cambiar los elementos por índice ni salir antes del bucle con break, salvo que uses otras construcciones.
Insertar un elemento por índice: Insert()
El método Insert() sirve si quieres añadir un elemento no al final, sino donde quieras. Por ejemplo, en la segunda posición:
numbers.Insert(1, 55); // Inserta 55 en la posición con índice 1
Ahora numbers: [42, 55, 200]
Eliminar elementos
Eliminar de la lista es casi tan fácil como añadir:
numbers.Remove(55); // Elimina la PRIMERA aparición de 55
numbers.RemoveAt(0); // Elimina el elemento con índice 0
numbers.Clear(); // Vacía la lista completamente
Detalles sobre eliminar:
- Si intentas eliminar un valor que no existe con Remove, no pasa nada malo: la lista no cambia, no hay errores.
- Tras Clear() la lista vuelve a estar vacía.
4. Detalles útiles
Cómo crece List<T>
Cuando añades elementos a List<T>, no aumenta el tamaño de uno en uno. En vez de eso, List<T> reserva un bloque de memoria con margen (la capacidad interna — Capacity).
Cuando hay más elementos que la capacidad actual, la lista reserva un bloque de memoria más grande (normalmente el doble) y copia allí todos los valores antiguos:
Añadimos elementos → cabe → cabe → cabe →
Nuevo elemento no cabe → se crea un array más grande → se copian los datos → seguimos
Esto permite añadir elementos más rápido, porque la memoria solo se reorganiza de vez en cuando, no en cada Add.
var list = new List<int>(); // Capacity = 0
list.Add(1); // Capacity = 4
list.Add(2); // Capacity = 4
list.Add(3); // Capacity = 4
list.Add(4); // Capacity = 4
list.Add(5); // Capacity = 8 (¡expansión!)
Capacity vs. Count: ¿en qué se diferencian?
- Count — cantidad de elementos añadidos a la lista.
- Capacity — tamaño del array interno que guarda los elementos.
List<int> nums = new List<int>();
Console.WriteLine(nums.Count); // 0
Console.WriteLine(nums.Capacity); // Normalmente 0 o un tamaño pequeño
nums.Add(123);
Console.WriteLine(nums.Count); // 1
Console.WriteLine(nums.Capacity); // Ahora puede ser mayor que Count
nums.Capacity = 1000; // Puedes aumentar Capacity a mano si sabes que habrá muchos elementos
La mayoría de programadores solo usan Count, pero si haces apps donde el rendimiento es clave (por ejemplo, juegos o análisis de datos), ajustar Capacity puede ser útil.
5. Ejemplo
Seguimos mejorando nuestra pequeña app — ahora, por ejemplo, el usuario puede añadir sus números favoritos y luego trabajar con la lista: insertar, eliminar, mostrar y vaciar.
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<int> favoriteNumbers = new List<int>();
Console.WriteLine("Introduce tus números favoritos (uno por línea, 0 — terminar):");
while (true)
{
int num = int.Parse(Console.ReadLine());
if (num == 0) break;
favoriteNumbers.Add(num);
}
Console.WriteLine("Tus números favoritos:");
foreach (int n in favoriteNumbers)
{
Console.Write(n + " ");
}
Console.WriteLine("\n¿Quieres eliminar el primer número? (y/n)");
if (Console.ReadLine().ToLower() == "y")
{
favoriteNumbers.RemoveAt(0);
}
Console.WriteLine("Ahora tu lista:");
foreach (int n in favoriteNumbers)
{
Console.Write(n + " ");
}
}
}
Nota: Aquí usamos int.Parse. En proyectos reales es mejor comprobar siempre la entrada con int.TryParse para que el programa no "petee" con cualquier error del usuario.
6. Errores y "trampas" típicas de principiantes
Muy típico: escribes un bucle de 0 a List.Count, pero durante el bucle (por ejemplo, en un foreach) cambias la lista (añades o eliminas elementos). ¡No lo hagas! Los pros de la programación llaman a esto "modificar la colección durante la iteración". En la mayoría de los casos te saltará una excepción tipo InvalidOperationException.
Ejemplo de mal código:
foreach (int n in numbers)
{
if (n < 0)
numbers.Remove(n); // ¡BOOM! Runtime error.
}
Correcto: o bien primero guardas los índices/elementos a eliminar en otra lista, o usas un bucle for en sentido inverso.
GO TO FULL VERSION