Capítulo 9: Colecciones, listas y arrays en C#

5 min de lectura

Parte I - Sección 2: Programación desde cero - Nivel: Principiante


🎯 Objetivos de aprendizaje

Al finalizar este capítulo serás capaz de:

  • Entender qué es una colección y por qué necesitamos más que variables simples
  • Trabajar con arrays de tamaño fijo
  • Usar List para colecciones dinámicas que crecen y decrecen
  • Manejar Dictionary<K,V> para pares clave-valor
  • Elegir la colección adecuada según el problema
  • Recorrer colecciones con foreach, for, y acceso directo
  • Construir un gestor completo de lista de materiales con búsqueda y filtrado

📋 Requisitos previos

Antes de comenzar este capítulo debes:

  • ✅ Haber completado el Capítulo 8
  • ✅ Entender funciones y métodos
  • ✅ Conocer bucles (for, foreach, while)
  • ✅ Saber trabajar con variables y tipos de datos

🏆 Proyecto del capítulo

Gestor Inteligente de Lista de Materiales

Al final de este capítulo habrás construido un sistema completo para:

  • Gestionar componentes eléctricos (agregar, eliminar, buscar)
  • Buscar por código, tipo, o designación
  • Calcular estadísticas (total de componentes, corriente total, etc.)
  • Filtrar por criterios (corriente, voltaje, fabricante)
  • Generar reportes formateados

Tiempo estimado: 60-70 minutos

⚠️ Nota Importante: En este capítulo, los datos de los componentes (designación, código, etc.) se crearán manualmente en el código. Esto se hace intencionalmente para centrarnos en el aprendizaje de Colecciones (Listas y Diccionarios) sin distraernos con la complejidad de leer datos reales de la base de datos de EPLAN, lo cual aprenderemos en capítulos posteriores.


📚 Contenido

1. Introducción: El problema de las variables individuales

Sin colecciones (código difícil de mantener):

// Necesitamos almacenar 100 componentes...
string componente1 = "K1";
string componente2 = "K2";
string componente3 = "K3";
// ... 97 variables más
string componente100 = "K100";

// ¿Cómo buscamos? ¿Cómo agregamos uno nuevo?
// Imposible de mantener

Con colecciones (código escalable):

// Array simple de 100 elementos
string[] componentes = new string[100];
componentes[0] = "K1";
componentes[1] = "K2";
// ...

// O mejor: List que crece dinámicamente
List<string> componentes = new List<string>();
componentes.Add("K1");
componentes.Add("K2");
// Fácil agregar, eliminar, buscar

Las colecciones nos permiten:

  • Almacenar múltiples valores relacionados
  • Agregar y eliminar elementos dinámicamente
  • Buscar y filtrar datos eficientemente
  • Iterar sobre todos los elementos fácilmente
  • Organizar datos complejos

2. Arrays: Colecciones de tamaño fijo

Un array es una colección de elementos del mismo tipo con tamaño fijo.

2.1 Declaración y creación

// Sintaxis
tipo[] nombreArray = new tipo[tamaño];

// Ejemplos
string[] componentes = new string[5];
double[] corrientes = new double[10];
int[] cantidades = new int[20];

2.2 Inicialización

Forma 1: Declarar y luego asignar

string[] componentes = new string[3];
componentes[0] = "K1";
componentes[1] = "Q1";
componentes[2] = "F1";

Forma 2: Inicializar directamente

string[] componentes = {"K1", "Q1", "F1"};
// O más explícito:
string[] componentes = new string[] {"K1", "Q1", "F1"};

Forma 3: Con valores calculados

double[] voltajes = new double[5];
for (int i = 0; i < voltajes.Length; i++)
{
    voltajes[i] = 230.0 + i * 10.0;  // 230, 240, 250, 260, 270
}

2.3 Acceso a elementos

string[] componentes = {"K1", "Q1", "F1", "M1", "X1"};

// Acceso por índice (comienza en 0)
string primero = componentes[0];      // "K1"
string ultimo = componentes[4];       // "X1"

// Modificar elemento
componentes[2] = "F2";                // Cambia "F1" por "F2"

// Longitud del array
int total = componentes.Length;       // 5

2.4 Recorrer arrays

Con for:

string[] componentes = {"K1", "Q1", "F1", "M1"};

for (int i = 0; i < componentes.Length; i++)
{
    MessageBox.Show(string.Format("[{0}] = {1}", i, componentes[i]));
}

Con foreach (más simple):

foreach (string componente in componentes)
{
    MessageBox.Show(componente);
}

2.5 Ejemplo práctico: Análisis de corrientes

string[] designaciones = {"K1", "K2", "Q1", "Q2", "F1"};
double[] corrientes = {25.0, 40.0, 16.0, 20.0, 10.0};

// Buscar la corriente máxima
double maxCorriente = corrientes[0];
string designacionMax = designaciones[0];

for (int i = 1; i < corrientes.Length; i++)
{
    if (corrientes[i] > maxCorriente)
    {
        maxCorriente = corrientes[i];
        designacionMax = designaciones[i];
    }
}

MessageBox.Show(string.Format("Corriente máxima: {0}A en {1}",
    maxCorriente, designacionMax));
// Corriente máxima: 40A en K2

2.6 Limitaciones de los arrays

Tamaño fijo: No se puede cambiar después de crear

string[] componentes = new string[5];
// No puedes agregar un sexto elemento

No se pueden eliminar elementos: Solo sobrescribir

componentes[2] = null;  // "Elimina" pero deja un hueco

No hay métodos convenientes como Add(), Remove(), Contains()

Solución: Usar List<T> para colecciones dinámicas.


3. List: Colecciones dinámicas

List<T> es una colección dinámica que puede crecer y decrecer automáticamente.

Ventajas sobre arrays:

  • Tamaño dinámico (crece automáticamente)
  • Métodos convenientes: Add(), Remove(), Insert(), Contains()
  • Más flexible para la mayoría de casos

3.1 Declaración y creación

// Sintaxis
List<tipo> nombreLista = new List<tipo>();

// Ejemplos
List<string> componentes = new List<string>();
List<double> corrientes = new List<double>();
List<int> cantidades = new List<int>();

3.2 Agregar elementos

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

// Add: Agrega al final
componentes.Add("K1");
componentes.Add("Q1");
componentes.Add("F1");

// Insert: Agrega en posición específica
componentes.Insert(1, "K2");  // Inserta "K2" en posición 1
// Resultado: ["K1", "K2", "Q1", "F1"]

3.3 Acceso a elementos

List<string> componentes = new List<string> {"K1", "Q1", "F1", "M1"};

// Acceso por índice
string primero = componentes[0];          // "K1"
string segundo = componentes[1];          // "Q1"

// Count: número de elementos
int total = componentes.Count;            // 4

// Contains: verificar si existe
bool existe = componentes.Contains("K1"); // true

3.4 Eliminar elementos

List<string> componentes = new List<string> {"K1", "Q1", "F1", "M1"};

// Remove: elimina la primera ocurrencia
componentes.Remove("Q1");
// Resultado: ["K1", "F1", "M1"]

// RemoveAt: elimina por índice
componentes.RemoveAt(0);
// Resultado: ["F1", "M1"]

// Clear: elimina todos
componentes.Clear();
// Resultado: []

3.5 Buscar y filtrar

List<string> componentes = new List<string> {"K1", "K2", "Q1", "Q2", "F1", "M1"};

// IndexOf: encontrar posición
int indice = componentes.IndexOf("Q1");  // 2
if (indice != -1)
{
    MessageBox.Show("Encontrado en posición: " + indice);
}

// Find: buscar con condición
string primerK = componentes.Find(c => c.StartsWith("K"));
MessageBox.Show("Primer contactor: " + primerK);  // "K1"

// FindAll: buscar todos que cumplan condición
List<string> contactores = componentes.FindAll(c => c.StartsWith("K"));
// Resultado: ["K1", "K2"]

3.6 Ejemplo práctico: Gestión de componentes

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

// Agregar componentes
componentes.Add("K1");
componentes.Add("K2");
componentes.Add("Q1");
componentes.Add("F1");

// Mostrar todos
string lista = "Componentes:\n";
foreach (string comp in componentes)
{
    lista += "- " + comp + "\n";
}
MessageBox.Show(lista);

// Buscar si existe un componente
if (componentes.Contains("Q1"))
{
    MessageBox.Show("Guardamotor Q1 encontrado");
}

// Contar contactores
int numContactores = componentes.FindAll(c => c.StartsWith("K")).Count;
MessageBox.Show("Número de contactores: " + numContactores);  // 2

// Eliminar componente
componentes.Remove("F1");
MessageBox.Show("Componentes después de eliminar F1: " + componentes.Count);  // 3

3.7 Recorrer List

Con foreach (recomendado):

List<double> corrientes = new List<double> {10.0, 16.0, 25.0, 32.0};

foreach (double corriente in corrientes)
{
    MessageBox.Show(string.Format("Corriente: {0}A", corriente));
}

Con for (cuando necesitas el índice):

for (int i = 0; i < corrientes.Count; i++)
{
    MessageBox.Show(string.Format("[{0}] = {1}A", i, corrientes[i]));
}

4. Dictionary<K,V>: Colecciones clave-valor

Dictionary<K,V> almacena pares clave-valor para búsquedas rápidas.

Analogía: Un diccionario de idiomas

  • Clave: Palabra en español
  • Valor: Traducción en inglés
  • Búsqueda rápida por clave

4.1 Declaración y creación

// Sintaxis
Dictionary<tipoClave, tipoValor> nombreDict = new Dictionary<tipoClave, tipoValor>();

// Ejemplos
Dictionary<string, double> corrientes = new Dictionary<string, double>();
Dictionary<string, string> fabricantes = new Dictionary<string, string>();
Dictionary<int, string> componentes = new Dictionary<int, string>();

4.2 Agregar elementos

Dictionary<string, double> corrientes = new Dictionary<string, double>();

// Add: agregar par clave-valor
corrientes.Add("K1", 25.0);
corrientes.Add("K2", 40.0);
corrientes.Add("Q1", 16.0);

// O inicialización directa
Dictionary<string, double> corrientes = new Dictionary<string, double>
{
    {"K1", 25.0},
    {"K2", 40.0},
    {"Q1", 16.0}
};

4.3 Acceso a valores

Dictionary<string, double> corrientes = new Dictionary<string, double>
{
    {"K1", 25.0},
    {"K2", 40.0},
    {"Q1", 16.0}
};

// Acceso directo por clave
double corrienteK1 = corrientes["K1"];  // 25.0

// Verificar si existe una clave
if (corrientes.ContainsKey("K2"))
{
    MessageBox.Show("K2 tiene corriente: " + corrientes["K2"]);
}

// TryGetValue: forma segura (no lanza excepción)
double valor;
if (corrientes.TryGetValue("Q1", out valor))
{
    MessageBox.Show("Q1: " + valor + "A");
}
else
{
    MessageBox.Show("Q1 no encontrado");
}

4.4 Modificar y eliminar

Dictionary<string, double> corrientes = new Dictionary<string, double>
{
    {"K1", 25.0},
    {"K2", 40.0}
};

// Modificar valor existente
corrientes["K1"] = 32.0;

// Eliminar entrada
corrientes.Remove("K2");

// Count: número de entradas
int total = corrientes.Count;  // 1

4.5 Recorrer Dictionary

Con foreach (pares clave-valor):

Dictionary<string, double> corrientes = new Dictionary<string, double>
{
    {"K1", 25.0},
    {"K2", 40.0},
    {"Q1", 16.0}
};

foreach (KeyValuePair<string, double> par in corrientes)
{
    MessageBox.Show(string.Format("{0}: {1}A", par.Key, par.Value));
}

Forma simplificada con var:

foreach (var par in corrientes)
{
    MessageBox.Show(string.Format("{0}: {1}A", par.Key, par.Value));
}

Solo claves o solo valores:

// Solo claves
foreach (string clave in corrientes.Keys)
{
    MessageBox.Show(clave);
}

// Solo valores
foreach (double valor in corrientes.Values)
{
    MessageBox.Show(valor + "A");
}

4.6 Ejemplo práctico: Base de datos de componentes

// Diccionario: Código → Descripción
Dictionary<string, string> baseDatos = new Dictionary<string, string>
{
    {"3RT2026-1BB40", "Contactor 3P 25A AC3"},
    {"3RT2036-1BB40", "Contactor 3P 40A AC3"},
    {"3RV2011-1JA10", "Guardamotor 3.5-5A"},
    {"LC1D09BD", "Contactor TeSys D 9A"}
};

// Buscar componente
string codigoBuscar = "3RT2026-1BB40";

if (baseDatos.ContainsKey(codigoBuscar))
{
    string descripcion = baseDatos[codigoBuscar];
    MessageBox.Show(string.Format("Código: {0}\nDescripción: {1}",
        codigoBuscar, descripcion));
}
else
{
    MessageBox.Show("Código no encontrado");
}

// Listar todos los componentes
string reporte = "BASE DE DATOS DE COMPONENTES\n\n";
foreach (var item in baseDatos)
{
    reporte += string.Format("{0}\n  → {1}\n\n", item.Key, item.Value);
}
MessageBox.Show(reporte);

5. Comparación de colecciones

CaracterísticaArrayListDictionary<K,V>
TamañoFijoDinámicoDinámico
AccesoPor índicePor índicePor clave
BúsquedaLenta (O(n))Lenta (O(n))Rápida (O(1))
AgregarNoSí (Add)Sí (Add)
EliminarNoSí (Remove)Sí (Remove)
Uso típicoDatos fijosListas dinámicasBúsquedas por clave

Cuándo usar cada una:

Array string[]:

  • Tamaño conocido y fijo
  • No necesitas agregar/eliminar
  • Ejemplo: Días de la semana, meses del año

List List<string>:

  • Tamaño variable
  • Necesitas agregar/eliminar frecuentemente
  • Ejemplo: Lista de componentes en un proyecto

Dictionary<K,V> Dictionary<string, double>:

  • Búsquedas rápidas por clave única
  • Asociar un valor con otro
  • Ejemplo: Código de artículo → Descripción

💻 Manos a la obra: Construyendo el proyecto

Vamos a crear un Gestor Inteligente de Lista de Materiales que combina todas las colecciones.

Estructura del proyecto

using System;
using System.Collections.Generic;
using Eplan.EplApi.Base;
using Eplan.EplApi.ApplicationFramework;

public class GestorListaMateriales
{
    // ========================================
    // ESTRUCTURAS DE DATOS
    // ========================================

    // Lista principal de componentes
    private List<Componente> componentes;

    // Diccionario para búsqueda rápida por código
    private Dictionary<string, Componente> indiceCodigo;

    // Estadísticas
    private Dictionary<string, int> contadorPorTipo;

    [Start]
    public void Ejecutar()
    {
        // Código principal
    }
}

// Clase auxiliar para representar un componente
public class Componente
{
    public string Designacion { get; set; }
    public string Codigo { get; set; }
    public string Tipo { get; set; }
    public double Corriente { get; set; }
    public double Voltaje { get; set; }
    public string Fabricante { get; set; }

    public Componente(string designacion, string codigo, string tipo,
                     double corriente, double voltaje, string fabricante)
    {
        this.Designacion = designacion;
        this.Codigo = codigo;
        this.Tipo = tipo;
        this.Corriente = corriente;
        this.Voltaje = voltaje;
        this.Fabricante = fabricante;
    }

    public override string ToString()
    {
        return string.Format("{0} - {1} ({2}A, {3}V)",
            Designacion, Codigo, Corriente, Voltaje);
    }
}

Implementación completa

El código completo está disponible en code/cap-09/GestorListaMateriales.cs.

Funcionalidades incluidas:

  1. Agregar componentes con validación
  2. Buscar por designación, código, o tipo
  3. Eliminar componentes
  4. Filtrar por corriente, voltaje, fabricante
  5. Estadísticas: Total, corriente total, por tipo
  6. Reportes formateados
  7. Validaciones (duplicados, valores incorrectos)

🔍 Deep Dive: Entendiendo en profundidad

Complejidad de búsqueda

Array y List: Búsqueda lineal O(n)

// Tiene que revisar todos los elementos
List<string> componentes = new List<string> {"K1", "K2", "Q1", "F1"};
bool existe = componentes.Contains("F1");  // O(n) - revisa 4 elementos

Dictionary<K,V>: Búsqueda por hash O(1)

// Acceso directo mediante hash
Dictionary<string, string> componentes = new Dictionary<string, string>
{
    {"K1", "Contactor"},
    {"Q1", "Guardamotor"}
};
bool existe = componentes.ContainsKey("K1");  // O(1) - acceso directo

Conclusión: Para búsquedas frecuentes, usa Dictionary.

Colecciones anidadas

Puedes combinar colecciones:

// Dictionary de Lists
Dictionary<string, List<string>> componentesPorTipo = new Dictionary<string, List<string>>
{
    {"Contactor", new List<string> {"K1", "K2", "K3"}},
    {"Guardamotor", new List<string> {"Q1", "Q2"}},
    {"Motor", new List<string> {"M1", "M2", "M3"}}
};

// Acceso
List<string> contactores = componentesPorTipo["Contactor"];
foreach (string contactor in contactores)
{
    MessageBox.Show(contactor);
}

LINQ: Consultas avanzadas

LINQ (Language Integrated Query) permite consultas poderosas:

List<Componente> componentes = ObtenerComponentes();

// Filtrar por corriente > 20A
var altaCorriente = componentes.Where(c => c.Corriente > 20.0).ToList();

// Ordenar por corriente
var ordenados = componentes.OrderBy(c => c.Corriente).ToList();

// Obtener solo designaciones
var designaciones = componentes.Select(c => c.Designacion).ToList();

// Suma total de corrientes
double corrienteTotal = componentes.Sum(c => c.Corriente);

// Corriente promedio
double corrientePromedio = componentes.Average(c => c.Corriente);

📝 Resumen

En este capítulo aprendiste:

  • Arrays: Colecciones de tamaño fijo, acceso rápido por índice
  • List: Colecciones dinámicas con Add/Remove/Insert
  • Dictionary<K,V>: Pares clave-valor para búsquedas rápidas
  • Cuándo usar cada una: Según necesidades de tamaño y búsqueda
  • Recorrer colecciones: foreach, for, acceso directo
  • Proyecto construido: Gestor completo de lista de materiales

Conceptos clave:

  • Las colecciones permiten manejar múltiples datos relacionados
  • List es la más versátil para la mayoría de casos
  • Dictionary<K,V> es ideal para búsquedas rápidas
  • Siempre valida antes de acceder (ContainsKey, TryGetValue)

🔗 Conexiones

⬅️ Capítulo anterior

Capítulo 8: Funciones (Métodos)

➡️ Próximo capítulo

Capítulo 10: Clases y objetos (POO)

En el próximo capítulo aprenderás los fundamentos de Programación Orientada a Objetos (POO): clases, objetos, propiedades, constructores, y métodos. Construirás un sistema completo de gestión de circuitos eléctricos usando principios de POO.


📚 Recursos adicionales


❓ Preguntas frecuentes

P: ¿Cuándo debería usar un array en lugar de List?

R: Usa arrays solo cuando el tamaño es fijo y conocido de antemano (ej. días de la semana: 7). En la mayoría de casos, List es mejor porque es más flexible.

P: ¿Puedo usar objetos complejos en colecciones?

R: Sí. Puedes tener List<Componente>, Dictionary<string, Componente>, etc.

P: ¿Qué pasa si accedo a un índice fuera de rango?

R: Se lanza una excepción IndexOutOfRangeException. Siempre verifica con if (i < lista.Count).

P: ¿Puedo modificar una lista mientras la recorro con foreach?

R: No. Esto causa una excepción. Si necesitas modificar, usa for en orden inverso o crea una lista temporal.

// Mal - causa excepción
foreach (var item in lista)
{
    lista.Remove(item);  // ❌ ERROR
}

// Bien - usar for inverso
for (int i = lista.Count - 1; i >= 0; i--)
{
    lista.RemoveAt(i);  // ✓ OK
}

P: ¿Dictionary permite claves duplicadas?

R: No. Cada clave debe ser única. Si intentas agregar una clave que ya existe, se lanza excepción. Usa ContainsKey() antes de agregar.


📋 Checklist de completitud

Antes de pasar al siguiente capítulo, asegúrate de:

  • Entender la diferencia entre Array, List, y Dictionary
  • Poder crear y manipular List (Add, Remove, Contains)
  • Poder usar Dictionary<K,V> para búsquedas rápidas
  • Saber recorrer colecciones con foreach y for
  • Comprender cuándo usar cada tipo de colección
  • Haber ejecutado el gestor de lista de materiales
  • Poder agregar nuevas funcionalidades al gestor
  • (Opcional) Haber intentado al menos un desafío

¿Listo para aprender Programación Orientada a Objetos? Vamos al Capítulo 10! 🚀


Última actualización: Enero 2025 Tiempo de lectura estimado: 60-70 minutos Código de ejemplo: code/cap-09/