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:
// ❌ 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:
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 riesgoT 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:
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 stringUn 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:
// 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:
| Constraint | Significa |
|---|---|
where T : class | T debe ser una clase (tipo por referencia) |
where T : struct | T debe ser un struct (por valor) |
where T : new() | T debe tener constructor sin parámetros |
where T : Component | T debe heredar de Component |
where T : IDañable | T 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.
// 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
objecty castear a mano cuando un genérico haría el trabajo con seguridad de tipos. - Olvidar el
wherecuando 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#
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.