| Syntax | <T>, where T : ... |
|---|---|
| Category | Type system |
| In Unity | GetComponent<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>();
| Constraint | Means |
|---|---|
where T : Component | T is Component or a subclass (any component type) |
where T : MonoBehaviour | T is one of your scripts |
where T : IDamageable | T implements the interface |
where T : class | T is a reference type |
where T : struct | T 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
- Generics are checked when you compile. That's where the safety comes from: wrong types never make it into the game.
- At runtime, each different
Tgets its own version of the class.List<int>stores realints directly, with no boxing (see Value & Reference Types). - Unity can serialize (show in the Inspector)
List<T>and, since Unity 2020.1, fields of other generic types such asUnityEvent<float>or your own[Serializable] class Stat<T>. It can't serializeDictionary.
Common Mistakes
GetComponent<IDamageable>()with a constraint that forbids it. Unity's ownGetComponent<T>allows interfaces, but if your helper sayswhere T : Component, interfaces won't compile. Drop the constraint or use a separate helper.- Trying to use members without a constraint. Inside
Pick<T>, you can't callitem.name, because T could be anything. Addwhere T : UnityEngine.Objectto use Unity object members. - Over-engineering. Make something generic when you actually have two or more types that need it, not before.
See also
This page is a simplified, AI-assisted interpretation. For the complete and authoritative reference, see the official Microsoft C# documentation.