Generics
Syntax<T>, where T : ...
CategoryType system
In UnityGetComponent<T>(), List<T>, UnityEvent<T>, ObjectPool<T>

Generics

Generics let you write a class or method once and use it with many types. The <T> is a placeholder for "some type, chosen later". You've already used them: GetComponent<Rigidbody>() and List<Enemy> are generics.

A generic is a labeled storage box with a blank label. List<T> is "a box for ___". When you write List<Coin> you fill in the blank, and now the box only accepts coins. Try to put a sword in it and the compiler stops you before the game even runs.

Using generics

Rigidbody rb = GetComponent<Rigidbody>();              // T = Rigidbody
List<Enemy> enemies = new List<Enemy>();               // T = Enemy
Dictionary<string, int> scores = new Dictionary<string, int>(); // two type parameters

enemies.Add(goblin);     // OK
// enemies.Add(coin);    // compile error: a Coin is not an Enemy

Before generics, collections stored object, so you had to cast everything back and could put the wrong thing in by accident. Generics catch those mistakes at compile time.

Writing a generic method

public static class RandomUtil
{
    // Works for an array of ANY type
    public static T Pick<T>(T[] items)
    {
        return items[Random.Range(0, items.Length)];
    }
}

// Usage: C# usually figures out T from the argument
AudioClip clip = RandomUtil.Pick(footstepClips);    // T = AudioClip
string line    = RandomUtil.Pick(greetings);        // T = string
GameObject pre = RandomUtil.Pick<GameObject>(enemyPrefabs); // or write T explicitly

Constraints: where T : ...

Sometimes your code needs T to be a certain kind of type. A constraint says so, and lets you use that type's members.

public static class GameObjectExtensions
{
    // T must be a Component, because AddComponent<T> only accepts components.
    // ("this GameObject go" makes it an extension method; see Extension Methods.)
    public static T GetOrAdd<T>(this GameObject go) where T : Component
    {
        if (!go.TryGetComponent(out T comp))
            comp = go.AddComponent<T>();
        return comp;
    }
}

// Usage
var audio = gameObject.GetOrAdd<AudioSource>();
ConstraintMeans
where T : ComponentT is Component or a subclass (any component type)
where T : MonoBehaviourT is one of your scripts
where T : IDamageableT implements the interface
where T : classT is a reference type
where T : structT is a value type
where T : new()T has a parameterless constructor, so you can write new T()

Writing a generic class

A generic singleton base class: every manager gets the singleton behavior by inheriting from it.

using UnityEngine;

public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    public static T Instance { get; private set; }

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

public class GameManager : Singleton<GameManager>
{
    public int Score { get; set; }
}

public class AudioManager : Singleton<AudioManager>
{
    public void Play(AudioClip clip) { /* ... */ }
}

// Usage
GameManager.Instance.Score += 10;
AudioManager.Instance.Play(coinSound);

Each Singleton<GameManager> and Singleton<AudioManager> is a separate class with its own Instance, because static fields are separate for each T.

What happens at runtime

  1. Generics are checked when you compile. That's where the safety comes from: wrong types never make it into the game.
  2. At runtime, each different T gets its own version of the class. List<int> stores real ints directly, with no boxing (see Value & Reference Types).
  3. Unity can serialize (show in the Inspector) List<T> and, since Unity 2020.1, fields of other generic types such as UnityEvent<float> or your own [Serializable] class Stat<T>. It can't serialize Dictionary.

Common Mistakes

See also