Colecciones

Intermedio
CSharp
CSharp
Actualizado: 23/09/2025

Arrays y listas

Las colecciones son estructuras fundamentales que nos permiten almacenar múltiples elementos del mismo tipo de forma organizada. En C# disponemos de varias opciones, siendo los arrays y las listas las más utilizadas para trabajar con secuencias de datos.

Arrays: almacenamiento de tamaño fijo

Los arrays son colecciones de elementos del mismo tipo con un tamaño fijo que se define en el momento de su creación. Una vez establecido su tamaño, no puede modificarse durante la ejecución del programa.

Declaración y inicialización de arrays:

// Declaración con tamaño específico
int[] numeros = new int[5];

// Inicialización con valores
string[] nombres = {"Ana", "Luis", "Carmen"};

// Sintaxis alternativa moderna
var colores = new[] {"rojo", "azul", "verde"};

Los arrays utilizan índices basados en cero, lo que significa que el primer elemento se encuentra en la posición 0:

string[] ciudades = {"Madrid", "Barcelona", "Valencia"};

Console.WriteLine(ciudades[0]); // Madrid
Console.WriteLine(ciudades[1]); // Barcelona
Console.WriteLine(ciudades[2]); // Valencia

Propiedades y operaciones básicas:

int[] puntuaciones = {85, 92, 78, 96, 88};

// Obtener el tamaño del array
Console.WriteLine($"Tamaño: {puntuaciones.Length}"); // 5

// Modificar un elemento
puntuaciones[2] = 80;

// Recorrer el array
for (int i = 0; i < puntuaciones.Length; i++)
{
    Console.WriteLine($"Posición {i}: {puntuaciones[i]}");
}

Listas: colecciones dinámicas

Las listas (List<T>) son colecciones dinámicas que pueden crecer o reducirse durante la ejecución. Ofrecen mayor flexibilidad que los arrays y proporcionan métodos útiles para manipular los elementos.

Declaración e inicialización de listas:

// Lista vacía
List<int> edades = new List<int>();

// Lista con valores iniciales
List<string> frutas = new List<string> {"manzana", "pera", "naranja"};

// Sintaxis moderna con var
var precios = new List<decimal> {19.99m, 25.50m, 12.75m};

Operaciones fundamentales:

var tareas = new List<string>();

// Agregar elementos
tareas.Add("Estudiar C#");
tareas.Add("Hacer ejercicios");
tareas.Add("Revisar código");

// Insertar en posición específica
tareas.Insert(1, "Leer documentación");

// Obtener el número de elementos
Console.WriteLine($"Total de tareas: {tareas.Count}");

// Acceder a elementos por índice
Console.WriteLine($"Primera tarea: {tareas[0]}");

Métodos útiles para buscar y verificar:

var numeros = new List<int> {10, 25, 30, 15, 40};

// Verificar si contiene un elemento
bool existe = numeros.Contains(25); // true

// Encontrar la posición de un elemento
int indice = numeros.IndexOf(30); // 2

// Verificar si está vacía
bool vacia = numeros.Count == 0; // false

Eliminación de elementos:

var productos = new List<string> {"laptop", "mouse", "teclado", "monitor"};

// Eliminar por valor
productos.Remove("mouse");

// Eliminar por índice
productos.RemoveAt(0);

// Limpiar toda la lista
productos.Clear();

Comparación práctica: Arrays vs Listas

La elección entre arrays y listas depende de las necesidades específicas de cada situación:

Cuándo usar arrays:

// Para datos con tamaño conocido y fijo
int[] diasDelMes = new int[12] {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

// Para operaciones que requieren máximo rendimiento
double[] coordenadas = new double[3] {x, y, z};

Cuándo usar listas:

// Para colecciones que crecen dinámicamente
var historialCompras = new List<string>();

// Cuando necesitamos métodos de manipulación
var resultados = new List<int>();
resultados.AddRange(new[] {1, 2, 3, 4, 5});

Recorrido de colecciones

Ambas estructuras soportan diferentes formas de iteración:

Bucle for tradicional:

var calificaciones = new List<double> {8.5, 9.2, 7.8, 9.6};

for (int i = 0; i < calificaciones.Count; i++)
{
    Console.WriteLine($"Nota {i + 1}: {calificaciones[i]}");
}

Bucle foreach (recomendado):

string[] materias = {"Matemáticas", "Historia", "Ciencias"};

foreach (string materia in materias)
{
    Console.WriteLine($"Materia: {materia}");
}

Trabajando con tipos personalizados

Las colecciones pueden almacenar cualquier tipo de datos, incluyendo objetos personalizados:

// Definición de una clase simple
public class Empleado
{
    public string Nombre { get; set; }
    public int Edad { get; set; }
    public decimal Salario { get; set; }
}

// Lista de objetos personalizados
var empleados = new List<Empleado>
{
    new Empleado { Nombre = "Ana", Edad = 28, Salario = 35000m },
    new Empleado { Nombre = "Carlos", Edad = 32, Salario = 42000m }
};

// Acceder a propiedades de los objetos
foreach (var empleado in empleados)
{
    Console.WriteLine($"{empleado.Nombre}: {empleado.Salario:C}");
}

Conjuntos, colas y pilas

Las colecciones especializadas nos proporcionan estructuras de datos optimizadas para casos de uso específicos. Los conjuntos, colas y pilas siguen patrones de acceso únicos que los hacen ideales para resolver problemas concretos de programación.

HashSet: colecciones de elementos únicos

Los conjuntos (HashSet<T>) garantizan que todos los elementos sean únicos y proporcionan operaciones rápidas de búsqueda, inserción y eliminación. Son especialmente útiles cuando necesitamos evitar duplicados o realizar operaciones matemáticas entre conjuntos.

Creación e inicialización:

// Conjunto vacío
var coloresUnicos = new HashSet<string>();

// Inicialización con valores
var numerosPrimos = new HashSet<int> {2, 3, 5, 7, 11, 13};

// Desde una lista existente (elimina duplicados automáticamente)
var numeros = new List<int> {1, 2, 2, 3, 3, 3, 4};
var numerosUnicos = new HashSet<int>(numeros); // {1, 2, 3, 4}

Operaciones básicas:

var etiquetas = new HashSet<string>();

// Agregar elementos (devuelve true si se agregó, false si ya existía)
bool agregado = etiquetas.Add("programación"); // true
bool duplicado = etiquetas.Add("programación"); // false

// Verificar existencia (operación muy rápida)
bool contiene = etiquetas.Contains("C#");

// Eliminar elementos
etiquetas.Remove("programación");

// Obtener el número de elementos
Console.WriteLine($"Total de etiquetas: {etiquetas.Count}");

Operaciones de conjuntos matemáticos:

var lenguajesBackend = new HashSet<string> {"C#", "Java", "Python", "Go"};
var lenguajesFrontend = new HashSet<string> {"JavaScript", "TypeScript", "C#"};

// Unión: elementos en cualquiera de los dos conjuntos
var todosLenguajes = new HashSet<string>(lenguajesBackend);
todosLenguajes.UnionWith(lenguajesFrontend);
// Resultado: {"C#", "Java", "Python", "Go", "JavaScript", "TypeScript"}

// Intersección: elementos comunes
var lenguajesComunes = new HashSet<string>(lenguajesBackend);
lenguajesComunes.IntersectWith(lenguajesFrontend);
// Resultado: {"C#"}

// Diferencia: elementos del primer conjunto que no están en el segundo
var soloBackend = new HashSet<string>(lenguajesBackend);
soloBackend.ExceptWith(lenguajesFrontend);
// Resultado: {"Java", "Python", "Go"}

Queue: colas FIFO (First In, First Out)

Las colas (Queue<T>) siguen el principio de "primero en entrar, primero en salir". Los elementos se agregan al final y se extraen desde el inicio, simulando una cola de personas esperando.

Operaciones fundamentales:

var colaProcesos = new Queue<string>();

// Agregar elementos al final de la cola
colaProcesos.Enqueue("Proceso A");
colaProcesos.Enqueue("Proceso B");
colaProcesos.Enqueue("Proceso C");

Console.WriteLine($"Elementos en cola: {colaProcesos.Count}"); // 3

Extracción y consulta de elementos:

var pedidos = new Queue<string>();
pedidos.Enqueue("Pedido #001");
pedidos.Enqueue("Pedido #002");
pedidos.Enqueue("Pedido #003");

// Ver el siguiente elemento sin extraerlo
string siguiente = pedidos.Peek(); // "Pedido #001"

// Extraer y procesar el primer elemento
while (pedidos.Count > 0)
{
    string pedidoActual = pedidos.Dequeue();
    Console.WriteLine($"Procesando: {pedidoActual}");
}

Caso práctico: sistema de turnos:

public class SistemaTurnos
{
    private Queue<string> colaEspera = new Queue<string>();
    
    public void AgregarCliente(string nombre)
    {
        colaEspera.Enqueue(nombre);
        Console.WriteLine($"{nombre} agregado a la cola. Posición: {colaEspera.Count}");
    }
    
    public string AtenderSiguiente()
    {
        if (colaEspera.Count > 0)
        {
            return colaEspera.Dequeue();
        }
        return "No hay clientes esperando";
    }
    
    public void MostrarEstado()
    {
        Console.WriteLine($"Clientes en espera: {colaEspera.Count}");
        if (colaEspera.Count > 0)
        {
            Console.WriteLine($"Siguiente cliente: {colaEspera.Peek()}");
        }
    }
}

Stack: pilas LIFO (Last In, First Out)

Las pilas (Stack<T>) funcionan bajo el principio de "último en entrar, primero en salir". Los elementos se agregan y extraen desde la misma posición (la cima), como una pila de platos.

Operaciones básicas:

var historialNavegacion = new Stack<string>();

// Agregar elementos a la cima
historialNavegacion.Push("Página de inicio");
historialNavegacion.Push("Catálogo de productos");
historialNavegacion.Push("Detalle del producto");

Console.WriteLine($"Páginas visitadas: {historialNavegacion.Count}"); // 3

Navegación y consulta:

var operaciones = new Stack<string>();
operaciones.Push("Abrir archivo");
operaciones.Push("Editar texto");
operaciones.Push("Cambiar formato");

// Ver el elemento superior sin extraerlo
string ultimaOperacion = operaciones.Peek(); // "Cambiar formato"

// Deshacer operaciones (extraer elementos)
while (operaciones.Count > 0)
{
    string operacion = operaciones.Pop();
    Console.WriteLine($"Deshaciendo: {operacion}");
}

Implementación práctica: calculadora con historial:

public class CalculadoraConHistorial
{
    private Stack<double> resultados = new Stack<double>();
    
    public void GuardarResultado(double resultado)
    {
        resultados.Push(resultado);
        Console.WriteLine($"Resultado guardado: {resultado}");
    }
    
    public double ObtenerAnterior()
    {
        if (resultados.Count > 0)
        {
            return resultados.Pop();
        }
        Console.WriteLine("No hay resultados anteriores");
        return 0;
    }
    
    public void MostrarHistorial()
    {
        Console.WriteLine("Historial de resultados:");
        var copia = new Stack<double>(resultados.Reverse());
        
        int posicion = 1;
        while (copia.Count > 0)
        {
            Console.WriteLine($"{posicion}. {copia.Pop()}");
            posicion++;
        }
    }
}

Recorrido de colecciones especializadas

Todas estas estructuras soportan iteración mediante foreach, manteniendo su orden característico:

HashSet (orden no garantizado):

var tecnologias = new HashSet<string> {"C#", "JavaScript", "Python"};

foreach (string tecnologia in tecnologias)
{
    Console.WriteLine($"Tecnología: {tecnologia}");
}

Queue y Stack (orden específico):

var tareas = new Queue<string>();
tareas.Enqueue("Tarea 1");
tareas.Enqueue("Tarea 2");

// Recorre desde el primer elemento agregado
foreach (string tarea in tareas)
{
    Console.WriteLine($"Pendiente: {tarea}");
}

var acciones = new Stack<string>();
acciones.Push("Acción 1");
acciones.Push("Acción 2");

// Recorre desde el último elemento agregado
foreach (string accion in acciones)
{
    Console.WriteLine($"En pila: {accion}");
}

Cuándo usar cada colección especializada

HashSet es ideal para:

  • Eliminar duplicados de una colección
  • Verificar pertenencia de elementos rápidamente
  • Operaciones matemáticas entre conjuntos
  • Almacenar identificadores únicos

Queue es perfecta para:

  • Sistemas de turnos o colas de espera
  • Procesamiento de tareas en orden de llegada
  • Algoritmos de búsqueda en anchura (BFS)
  • Buffers de datos

Stack es útil para:

  • Funciones de deshacer/rehacer
  • Evaluación de expresiones matemáticas
  • Navegación con historial
  • Algoritmos de búsqueda en profundidad (DFS)

Estas estructuras especializadas complementan a los arrays y listas, proporcionando soluciones optimizadas para patrones específicos de acceso a datos. Su correcta elección puede mejorar significativamente tanto el rendimiento como la claridad del código.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en CSharp

Documentación oficial de CSharp
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, CSharp es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de CSharp

Explora más contenido relacionado con CSharp y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Comprender la diferencia entre arrays y listas en C# y cuándo usar cada uno.
  • Aprender a declarar, inicializar y manipular arrays y listas.
  • Conocer las colecciones especializadas HashSet, Queue y Stack y sus patrones de acceso.
  • Aplicar métodos para agregar, eliminar, buscar y recorrer elementos en diferentes colecciones.
  • Identificar casos prácticos para el uso adecuado de cada tipo de colección en programación.