Colecciones en C#: Lista, Diccionario, Cola, Pila
Aprende colecciones en C# como List, Dictionary, Queue y Stack para gestionar datos de forma eficiente con ejemplos.
En C#, las colecciones se utilizan para almacenar múltiples valores de forma dinámica y procesarlos de manera eficiente.
Los arreglos tienen un tamaño fijo, pero las colecciones pueden crecer o reducirse según sea necesario.
Este artículo cubre List<T>, Dictionary<TKey,TValue> (y KeyValuePair<,>),
SortedList<TKey,TValue>, Queue<T>, Stack<T>,
HashSet<T> y LinkedList<T> con ejemplos.
List<T>
List<T> es una lista dinámica que almacena elementos del mismo tipo en orden.
Ofrece muchos métodos para agregar/eliminar, buscar y ordenar.
using System;
using System.Collections.Generic;
using System.Linq;
var numeros = new List<int> { 5, 1, 9 };
numeros.Add(3); // agregar al final
numeros.Insert(1, 7); // insertar en un índice dado
numeros.Remove(9); // eliminar por valor (primera coincidencia)
numeros.RemoveAll(x => x % 2 == 1); // eliminar todos los impares
numeros.AddRange(new[] { 10, 2, 8 });
numeros.Sort(); // ordenar ascendente
numeros.Reverse(); // invertir orden
Console.WriteLine(string.Join(", ", numeros));
Console.WriteLine(numeros.Contains(8)); // true
// Filtrado con LINQ
var pares = numeros.Where(n => n % 2 == 0).ToList();
Console.WriteLine(string.Join(", ", pares));
Dictionary<TKey,TValue> y KeyValuePair<TKey,TValue>
Dictionary almacena pares clave–valor. Las claves son únicas.
Está basado en hash y permite búsquedas muy rápidas.
using System;
using System.Collections.Generic;
var precios = new Dictionary<string, decimal>(StringComparer.OrdinalIgnoreCase)
{
["Manzana"] = 5.75m,
["Pera"] = 7.25m
};
// Agregar o actualizar
precios["Banana"] = 12m; // agrega si no existe, actualiza si ya existe
// Lectura segura
if (precios.TryGetValue("MANZANA", out var valor))
Console.WriteLine($"Manzana: {valor} EUR");
// Verificar existencia de clave
if (!precios.ContainsKey("Cereza"))
precios.Add("Cereza", 18.5m);
// Iteración con KeyValuePair
foreach (KeyValuePair<string, decimal> kv in precios)
Console.WriteLine($"{kv.Key} → {kv.Value} EUR");
// Actualizar un valor
precios["Manzana"] = precios["Manzana"] * 1.10m; // +10% precio
Consejo: KeyValuePair<TKey,TValue> proporciona .Key y .Value
al recorrer un diccionario. Para actualizar, use directamente dict[key].
SortedList<TKey,TValue>
SortedList mantiene las claves en orden ascendente y permite acceso por índice a través de Keys y Values.
Combina el comportamiento de un diccionario y un arreglo.
using System;
using System.Collections.Generic;
var listaPrecios = new SortedList<string, decimal>(StringComparer.OrdinalIgnoreCase)
{
["Pera"] = 7.25m,
["Manzana"] = 5.75m,
["Banana"] = 12m,
["Cereza"] = 18.5m
};
// Claves ordenadas:
Console.WriteLine(string.Join(", ", listaPrecios.Keys)); // Banana, Cereza, Manzana, Pera
// Acceso por índice:
for (int i = 0; i < listaPrecios.Count; i++)
Console.WriteLine($"#{i} {listaPrecios.Keys[i]} → {listaPrecios.Values[i]}");
// Comparador personalizado (orden descendente):
var descendente = new SortedList<string, decimal>(Comparer<string>.Create((a,b) => StringComparer.OrdinalIgnoreCase.Compare(b,a)));
descendente["Manzana"] = 5.75m;
descendente["Pera"] = 7.25m;
Console.WriteLine(string.Join(", ", descendente.Keys)); // orden inverso
SortedListmueve elementos en inserciones/eliminaciones para mantener el orden. Si hay muchas operaciones, useSortedDictionary.- Si necesita rangos basados en índice,
SortedListes ventajosa.
Queue<T> (FIFO)
Queue funciona con el principio First In, First Out; útil para colas de tareas o flujos de trabajo.
using System;
using System.Collections.Generic;
var pedidos = new Queue<string>();
pedidos.Enqueue("Pedido#1001");
pedidos.Enqueue("Pedido#1002");
pedidos.Enqueue("Pedido#1003");
Console.WriteLine(pedidos.Peek()); // siguiente sin quitar: Pedido#1001
Console.WriteLine(pedidos.Dequeue()); // elimina: Pedido#1001
Console.WriteLine(pedidos.Count); // 2
Stack<T> (LIFO)
Stack funciona con el principio Last In, First Out; usado en deshacer (undo), historial, etc.
using System;
using System.Collections.Generic;
var pasos = new Stack<string>();
pasos.Push("Paso-1");
pasos.Push("Paso-2");
pasos.Push("Paso-3");
Console.WriteLine(pasos.Peek()); // Paso-3
Console.WriteLine(pasos.Pop()); // Paso-3 (eliminado)
Console.WriteLine(pasos.Pop()); // Paso-2
HashSet<T>
HashSet almacena una colección de elementos únicos. Los duplicados se ignoran.
Ideal para operaciones de conjuntos (unión, intersección, diferencia).
using System;
using System.Collections.Generic;
var a = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "manzana", "pera", "banana" };
var b = new HashSet<string> { "Banana", "cereza" };
var union = new HashSet<string>(a);
union.UnionWith(b); // A ∪ B
var interseccion = new HashSet<string>(a);
interseccion.IntersectWith(b); // A ∩ B
var diferencia = new HashSet<string>(a);
diferencia.ExceptWith(b); // A \ B
Console.WriteLine(string.Join(", ", union)); // manzana, pera, banana, cereza
Console.WriteLine(string.Join(", ", interseccion)); // banana
Console.WriteLine(string.Join(", ", diferencia)); // manzana, pera
Console.WriteLine(a.Add("Manzana")); // false (ya existe, insensible a mayúsculas/minúsculas)
Consejo: Las pruebas de pertenencia (Contains) y las inserciones son normalmente O(1) amortizado en HashSet; excelente para filtrar duplicados en grandes volúmenes de datos.
LinkedList<T>
LinkedList es una lista doblemente enlazada. Insertar/eliminar en nodos conocidos cuesta O(1).
No permite acceso aleatorio por índice; hay que recorrerla.
using System;
using System.Collections.Generic;
var lista = new LinkedList<string>();
var n1 = lista.AddFirst("Inicio");
var n2 = lista.AddLast("Fin");
lista.AddAfter(n1, "Medio-1"); // Inicio <-> Medio-1 <-> Fin
lista.AddBefore(n2, "Medio-2"); // Inicio <-> Medio-1 <-> Medio-2 <-> Fin
// Eliminar un nodo
var medio1 = n1.Next;
lista.Remove(medio1);
// Recorrer
for (var n = lista.First; n != null; n = n.Next)
Console.WriteLine(n.Value); // Inicio, Medio-2, Fin
- Rápido para operaciones en cabeza/cola y para insertar/eliminar alrededor de un nodo conocido.
- No apto para acceso aleatorio por índice; en ese caso usar
List<T>.
Aplicación de ejemplo: Flujo de procesamiento de pedidos
En el siguiente escenario, se combinan diferentes colecciones: Queue para pedidos pendientes, Dictionary para stock y precios, HashSet para clientes únicos, Stack para deshacer (undo), SortedList para la lista de precios y LinkedList para “productos vistos recientemente”.
using System;
using System.Collections.Generic;
var pendientes = new Queue<string>(new[] { "ORD-1001", "ORD-1002" });
var stock = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
["Manzana"] = 100, ["Pera"] = 50, ["Banana"] = 30
};
var precios = new SortedList<string, decimal> { ["Pera"] = 7.25m, ["Manzana"] = 5.75m, ["Banana"] = 12m };
var clientes = new HashSet<string>(); // IDs únicos de clientes
var undo = new Stack<Action>(); // pila de deshacer
var recientes = new LinkedList<string>(); // productos vistos recientemente
void AgregarAlCarrito(string producto, int cantidad)
{
if (stock.TryGetValue(producto, out var s) && s >= cantidad)
{
stock[producto] = s - cantidad;
undo.Push(() => stock[producto] += cantidad); // acción de deshacer
recientes.AddFirst(producto);
Console.WriteLine($"{cantidad} x {producto} agregado. Restante: {stock[producto]}");
}
else
{
Console.WriteLine($"Stock insuficiente: {producto}");
}
}
void ProcesarPedido(string noPedido, string clienteId)
{
Console.WriteLine($"Procesando: {noPedido}");
clientes.Add(clienteId);
AgregarAlCarrito("Manzana", 3);
AgregarAlCarrito("Pera", 2);
}
while (pendientes.Count > 0)
{
var pedido = pendientes.Dequeue();
ProcesarPedido(pedido, "CLI-001");
}
Console.WriteLine($"Clientes únicos: {clientes.Count}");
Console.WriteLine($"Lista de precios (ordenada): {string.Join(", ", precios.Keys)}");
// Deshacer si es necesario:
if (undo.Count > 0)
{
var accion = undo.Pop();
accion();
}
Console.WriteLine("Productos vistos recientemente (Top 3):");
int k = 0;
for (var n = recientes.First; n != null && k < 3; n = n.Next, k++)
Console.WriteLine($"- {n.Value}");
Resumen (TL;DR)
- List<T>: Lista dinámica, basada en índice. Ideal para acceso aleatorio.
- Dictionary<TKey,TValue>: Pares clave–valor, búsqueda rápida. Iterar con KeyValuePair.
- SortedList<TKey,TValue>: Claves ordenadas + acceso por índice. Buena para consultas por rango.
- Queue<T> (FIFO): Flujos de trabajo/colas.
- Stack<T> (LIFO): Deshacer, historial.
- HashSet<T>: Elementos únicos; operaciones de conjuntos (∪, ∩, \).
- LinkedList<T>: Lista enlazada; inserción/eliminación rápida en cabeza/cola o alrededor de un nodo conocido.
Guía: Acceso aleatorio → List, búsqueda rápida por clave → Dictionary, claves ordenadas + índice → SortedList, unicidad → HashSet, FIFO → Queue, LIFO → Stack, operaciones rápidas en cabeza/cola → LinkedList.
Artículos relacionados
Arreglos (Arrays) en C#
Aprende arreglos en C#: declaración, índices, recorridos con bucles y operaciones básicas con ejemplos prácticos.
Bucles en C# (for, foreach, while, do-while)
Aprende a usar los bucles for, foreach, while y do-while en C# para gestionar acciones repetitivas con ejemplos prácticos.
Expresiones Lambda en C#
Aprende expresiones lambda en C#, incluyendo sintaxis concisa, delegados Func y Action y ejemplos prácticos con LINQ.
Genéricos en C# (List<T>, Dictionary<TKey,TValue>)
Aprende genéricos en C# (List<T>, Dictionary<TKey,TValue>) para escribir código reutilizable y seguro de tipos con ejemplos.