Generics en C#: para qué sirven y cómo usarlos en la práctica

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Generics en C#: para qué sirven y cómo usarlos en la práctica



Los Generics (generalizaciones) son una de las capacidades clave del lenguaje C# que permite escribir código flexible, reutilizable y con seguridad de tipos. En este artículo analizaremos para qué sirven las generalizaciones, cómo funcionan internamente y mostraremos ejemplos prácticos de su uso en proyectos reales.



¿Qué son los Generics y para qué sirven?



Imagina que necesitas implementar un método que intercambie dos valores. Sin generalizaciones, tendrías que escribir versiones separadas para cada tipo de dato:



// Sin Generics — duplicación de códigovoid SwapInt(ref int a, ref int b) { int temp = a; a = b; b = temp; }void SwapString(ref string a, ref string b) { string temp = a; a = b; b = temp; }// ... y así para cada tipo


Con Generics este problema se resuelve de manera elegante:



// Con Generics — un método para todos los tiposvoid Swap<T>(ref T a, ref T b){    T temp = a;    a = b;    b = temp;}

// Usoint x = 1, y = 2;Swap(ref x, ref y); // T = int

string s1 = "Hola", s2 = "Mundo";Swap(ref s1, ref s2); // T = string


Principales ventajas de los Generics:

  • Seguridad de tipos — los errores de tipo se detectan en tiempo de compilación
  • Rendimiento — sin boxing/unboxing para tipos de valor
  • Reutilización de código — una clase/método funciona con cualquier tipo
  • Legibilidad — el código se vuelve más claro y conciso


Clases y estructuras genéricas



El ejemplo más común de uso de Generics son las colecciones. En lugar de escribir tu propia clase para cada tipo, creamos una generalizada:



public class MyList<T>{    private T[] _items;    private int _count;

public MyList(int capacity = 4) { _items = new T[capacity]; _count = 0; }

public void Add(T item) { if (_count == _items.Length) { Array.Resize(ref _items, _items.Length * 2); } _items[_count++] = item; }

public T Get(int index) { if (index < 0 || index >= _count) throw new IndexOutOfRangeException(); return _items[index]; }

public int Count => _count;}

// Usovar numbers = new MyList<int>();numbers.Add(10);numbers.Add(20);

var names = new MyList<string>();names.Add("Alicia");


Observa: al crear una instancia, especificamos el tipo concreto entre corchetes angulares <T>. El compilador sustituye este tipo en todos los lugares donde se usa T.



Métodos genéricos y restricciones (constraints)



A veces es necesario imponer restricciones al tipo T. Por ejemplo, para garantizar que el tipo implemente una interfaz determinada o tenga un constructor sin parámetros:



// Restricción: T debe implementar IComparable<T>public T GetMax<T>(T a, T b) where T : IComparable<T>{    return a.CompareTo(b) > 0 ? a : b;}

// Restricción: T debe tener un constructor sin parámetrospublic class Factory<T> where T : new(){ public T Create() { return new T(); }}

// Restricción: T debe ser un tipo de valor (struct)public struct Nullable<T> where T : struct{ private T _value; private bool _hasValue;

public Nullable(T value) { _value = value; _hasValue = true; }

public T Value => _hasValue ? _value : throw new InvalidOperationException(); public bool HasValue => _hasValue;}


Tipos principales de restricciones: