En esta página
Genéricos y colecciones en C#
¿Por qué genéricos?
Sin genéricos, tendrías que escribir una clase ListaDeEnteros, ListaDeStrings, ListaDeProductos... Los genéricos permiten escribir código reutilizable que funciona con cualquier tipo, con verificación de tipos en tiempo de compilación.
// Sin genéricos — usa object, pierde type safety
var listaSinGenericos = new System.Collections.ArrayList();
listaSinGenericos.Add(42);
listaSinGenericos.Add("error"); // compila pero es incorrecto
int num = (int)listaSinGenericos[0]; // cast manual necesario
// Con genéricos — type safe, sin cast
var listaConGenericos = new List<int>();
listaConGenericos.Add(42);
// listaConGenericos.Add("error"); // ← Error de compilación
int numSeguro = listaConGenericos[0]; // sin castList\
La colección más usada en .NET. Es un arreglo dinámico con tiempo de acceso O(1) por índice:
var numeros = new List<int> { 5, 2, 8, 1, 9, 3 };
// Agregar
numeros.Add(7);
numeros.AddRange(new[] { 10, 11, 12 });
// Eliminar
numeros.Remove(5); // primera ocurrencia del valor
numeros.RemoveAt(0); // por índice
numeros.RemoveAll(n => n > 9); // eliminar varios con predicado
// Buscar
int idx = numeros.IndexOf(8);
bool hay = numeros.Contains(8);
int? max = numeros.Count > 0 ? numeros.Max() : null;
// Ordenar
numeros.Sort(); // orden natural
numeros.Sort((a, b) => b.CompareTo(a)); // descendente
// Convertir
int[] arr = numeros.ToArray();
IReadOnlyList<int> ro = numeros.AsReadOnly();Dictionary\
Mapa clave-valor con búsqueda O(1). La clave debe ser única:
// Formas de inicializar
var paises = new Dictionary<string, string>
{
{ "BO", "Bolivia" },
{ "AR", "Argentina" },
{ "CL", "Chile" }
};
// Alternativa con indexer
var capitales = new Dictionary<string, string>
{
["BO"] = "Sucre",
["AR"] = "Buenos Aires",
["CL"] = "Santiago"
};
// Operaciones
capitales["PE"] = "Lima"; // agregar
capitales["BO"] = "Sucre/La Paz"; // actualizar
bool tiene = capitales.ContainsKey("BR");
// Lectura segura con TryGetValue (sin excepción si no existe)
if (capitales.TryGetValue("BO", out string? capital))
Console.WriteLine($"Capital: {capital}");
// GetValueOrDefault — valor por defecto si no existe
string? pais = capitales.GetValueOrDefault("XX", "Desconocido");
// Iterar
foreach (KeyValuePair<string, string> kvp in capitales)
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
// Deconstrucción (C# moderno)
foreach (var (codigo, nombre) in capitales)
Console.WriteLine($"{codigo} → {nombre}");HashSet\
Colección de elementos únicos, búsqueda O(1). Ideal para verificar pertenencia:
var roles = new HashSet<string> { "admin", "editor", "viewer" };
roles.Add("admin"); // ya existe — no añade duplicado
roles.Add("manager"); // nuevo — se añade
bool esAdmin = roles.Contains("admin"); // true, O(1)
// Operaciones de conjuntos
var rolesRequeridos = new HashSet<string> { "admin", "super" };
bool tieneAcceso = rolesRequeridos.IsSubsetOf(roles); // false
// UnionWith, IntersectWith, ExceptWith
var a = new HashSet<int> { 1, 2, 3, 4 };
var b = new HashSet<int> { 3, 4, 5, 6 };
var union = new HashSet<int>(a); union.UnionWith(b); // {1,2,3,4,5,6}
var intersect = new HashSet<int>(a); intersect.IntersectWith(b); // {3,4}
var diferencia = new HashSet<int>(a); diferencia.ExceptWith(b); // {1,2}Queue\ y Stack\
// Queue<T> — FIFO (First In, First Out)
var cola = new Queue<string>();
cola.Enqueue("Tarea 1");
cola.Enqueue("Tarea 2");
cola.Enqueue("Tarea 3");
string siguiente = cola.Dequeue(); // "Tarea 1" — extrae
string ver = cola.Peek(); // "Tarea 2" — solo mira
Console.WriteLine($"En cola: {cola.Count}"); // 2
// Stack<T> — LIFO (Last In, First Out)
var historial = new Stack<string>();
historial.Push("/home");
historial.Push("/cursos");
historial.Push("/cursos/dotnet");
string actual = historial.Pop(); // "/cursos/dotnet" — extrae
string anterior = historial.Peek(); // "/cursos" — solo mira
Console.WriteLine($"Navegaste a: {actual}");Clases genéricas propias
// Restricciones de tipo
public class Cache<TKey, TValue>
where TKey : notnull // TKey no puede ser null
where TValue : class // TValue es tipo de referencia
{
private readonly Dictionary<TKey, TValue> _store = new();
private readonly int _maxSize;
public Cache(int maxSize = 100) => _maxSize = maxSize;
public void Set(TKey key, TValue value)
{
if (_store.Count >= _maxSize)
throw new InvalidOperationException("Cache lleno");
_store[key] = value;
}
public TValue? Get(TKey key)
=> _store.TryGetValue(key, out var val) ? val : null;
public bool TryGet(TKey key, out TValue? value)
=> _store.TryGetValue(key, out value);
public void Invalidar(TKey key) => _store.Remove(key);
public void LimpiarTodo() => _store.Clear();
public int Tamaño => _store.Count;
}
// Uso
var cache = new Cache<string, string>(50);
cache.Set("token_1", "eyJhbGc...");
string? token = cache.Get("token_1");Métodos genéricos
// Intercambiar dos valores
static void Intercambiar<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
// Método con restricción de interfaz
static T Mayor<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) >= 0 ? a : b;
// Uso
int x = 5, y = 10;
Intercambiar(ref x, ref y);
Console.WriteLine($"x={x}, y={y}"); // x=10, y=5
Console.WriteLine(Mayor(3, 7)); // 7
Console.WriteLine(Mayor("Ana", "Zoe")); // ZoeIEnumerable\
La interfaz más fundamental de las colecciones. Si un tipo implementa IEnumerable<T>, puedes iterarlo con foreach y usar LINQ:
// Generador con yield return — evalúa de forma diferida (lazy)
static IEnumerable<int> GenerarPares(int limite)
{
for (int i = 0; i <= limite; i += 2)
yield return i;
}
foreach (int par in GenerarPares(20))
Console.Write($"{par} "); // 0 2 4 6 8 10 12 14 16 18 20
// Combinar con LINQ
var pares = GenerarPares(100)
.Where(n => n % 4 == 0)
.Take(5)
.ToList();Práctica
- Inventario: Crea un
Dictionary<string, int>de productos y cantidades. Agrega métodos para Agregar, Retirar (verificando stock) y Listar solo los que tienen stock > 0. - Clase genérica Pila: Implementa
public class Pila<T>conPush,Pop,PeekyCountusando internamente unaList<T>. - HashSet deduplicar: Dado un
List<string>con correos electrónicos duplicados, usaHashSet<string>para obtener la lista sin duplicados en una sola línea.
En la siguiente lección aprenderemos LINQ: la forma más elegante de consultar, transformar y agregar colecciones en C#.
Usa IReadOnlyList para exponer colecciones
Cuando una clase exponga su colección interna, devuelve IReadOnlyList<T> o IEnumerable<T> en lugar de List<T>. Esto previene que el código externo modifique la colección interna directamente y protege el invariante de la clase.
HashSet para unicidad, SortedDictionary para orden
HashSet<T> garantiza elementos únicos con búsqueda O(1). SortedDictionary<K,V> mantiene las claves ordenadas con búsqueda O(log n). Elige la colección según las operaciones que más harás: inserción/búsqueda frecuente → Dictionary; orden garantizado → SortedDictionary.
Restricciones where T
Las restricciones where T: class (tipo de referencia), where T: struct (tipo de valor), where T: new() (tiene constructor sin parámetros), where T: IInterfaz (implementa interfaz) y where T: ClaseBase ayudan al compilador a permitir operaciones específicas sobre T y a generar código más eficiente.
using System.Collections.Generic;
// ── List<T> — colección ordenada, tamaño dinámico ────
var productos = new List<string> { "Laptop", "Mouse", "Teclado" };
productos.Add("Monitor");
productos.Insert(1, "Webcam"); // insertar en posición
productos.Remove("Mouse"); // eliminar por valor
productos.RemoveAt(0); // eliminar por índice
Console.WriteLine($"Total: {productos.Count}");
Console.WriteLine(string.Join(", ", productos));
// Ordenar
productos.Sort();
productos.Sort((a, b) => b.Length.CompareTo(a.Length)); // custom
// Buscar
bool tiene = productos.Contains("Monitor");
int indice = productos.IndexOf("Monitor");
string? primero = productos.Find(p => p.StartsWith("M"));
// ── Dictionary<K,V> — clave-valor, O(1) lookup ──────
var precios = new Dictionary<string, decimal>
{
["Laptop"] = 1299.99m,
["Mouse"] = 29.99m,
["Monitor"] = 449.99m,
};
precios["Teclado"] = 89.99m; // añadir o actualizar
if (precios.TryGetValue("Laptop", out decimal precio))
Console.WriteLine($"Laptop: ${precio:F2}");
foreach (var (clave, valor) in precios)
Console.WriteLine($" {clave}: ${valor:F2}");
Inicia sesión para guardar tu progreso