🎮 UnityDocs

Genéricos en C#

Qué son los genéricos (el <T> de List<T>) y cómo escribir métodos y clases que funcionan con cualquier tipo sin duplicar código ni perder seguridad de tipos. Con constraints y ejemplos de Unity.

📄 ArtículoAvanzado✅ RevisadoUnity 6.0 LTSActualizado 2026-07-15
DificultadConceptual Código Matemáticas
Tiempo📖 Lectura 7 min · ⌨️ Práctica 20 min

Ya los has usado#

Cada vez que escribes List<int> o Dictionary<string, int>, usas genéricos. Ese <...> dice "una lista de enteros", "un diccionario de string a int". Los genéricos permiten escribir código que funciona con cualquier tipo sin duplicarlo y sin perder la seguridad de tipos.

El problema que resuelven#

Sin genéricos, para una "caja" que guarde cualquier cosa usarías object (el tipo base de todo), y perderías el tipo:

C#
// ❌ Sin genéricos: pierdes el tipo, necesitas castear, y puede fallar
object caja = 5;
int n = (int)caja;      // hay que castear a mano
// object caja2 = "hola"; int m = (int)caja2;  // ¡explota en runtime!

Con un genérico, el tipo viaja contigo:

C#
public class Caja<T>
{
    private T contenido;
    public void Guardar(T valor) => contenido = valor;
    public T Sacar() => contenido;
}

Caja<int> caja = new Caja<int>();
caja.Guardar(5);
int n = caja.Sacar();   // ya es int, sin castear ni riesgo

T es un marcador de posición para el tipo, que se concreta al usar la clase.

Métodos genéricos#

Un solo método que sirve para cualquier tipo:

C#
T PrimeroDe<T>(T[] array)
{
    return array[0];
}

int x = PrimeroDe(new[] { 3, 5, 7 });        // T es int
string s = PrimeroDe(new[] { "a", "b" });    // T es string

Un ejemplo muy de Unity: un buscador seguro de componentes que devuelve el tipo correcto ya tipado.

Constraints: poner condiciones a T#

A veces necesitas que T cumpla algo (que sea una clase, que tenga constructor, que implemente una interfaz). Eso se hace con where:

C#
// T debe ser un Component (así puedo usar cosas de Component)
public T Crear<T>() where T : Component
{
    GameObject go = new GameObject(typeof(T).Name);
    return go.AddComponent<T>();
}

Rigidbody rb = Crear<Rigidbody>();

Constraints habituales:

ConstraintSignifica
where T : classT debe ser una clase (tipo por referencia)
where T : structT debe ser un struct (por valor)
where T : new()T debe tener constructor sin parámetros
where T : ComponentT debe heredar de Component
where T : IDañableT debe implementar esa interfaz

Dónde brillan en Unity#

  • Object pooling genérico: un ObjectPool<T> que sirve para balas, enemigos o lo que sea (ver object pooling).
  • Singleton genérico: una clase base Singleton<T> de la que hereden tus gestores sin repetir el patrón.
  • Utilidades: métodos que operan sobre cualquier lista o componente sin duplicar código.
C#
// Singleton genérico reutilizable
public class Singleton<T> : MonoBehaviour where T : Component
{
    public static T Instance { get; private set; }

    protected virtual void Awake()
    {
        if (Instance != null && Instance != this as T) { Destroy(gameObject); return; }
        Instance = this as T;
    }
}

// Uso: hereda y ya tienes el patrón hecho
public class GameManager : Singleton<GameManager> { /* ... */ }

Errores frecuentes#

  • Usar object y castear a mano cuando un genérico haría el trabajo con seguridad de tipos.
  • Olvidar el where cuando dentro del genérico necesitas capacidades del tipo (dará error al usar métodos que T "no sabe que tiene").
  • Sobrecomplicar: no todo necesita ser genérico. Úsalo cuando de verdad reutilizas para varios tipos.

Ponte a prueba#

📝 Pon a prueba lo aprendido

Resumen#

Los genéricos (<T>) permiten escribir clases y métodos que funcionan con cualquier tipo sin duplicar código ni perder la seguridad de tipos (por eso List<T> es mejor que una lista de object). Usa constraints (where T : ...) cuando necesites capacidades concretas de T. En Unity son la base de pools, singletons y utilidades reutilizables. No los fuerces: úsalos cuando de verdad reutilizas para varios tipos.

Fuentes y para profundizar