What <T> means
The shelf from the earlier lessons had a label: only numbers go on a List<int>, only strings on a List<string>. Now picture a shelf with no label, where anything goes: books, mugs, boxes. Putting a thing there is no trouble. But each time you take a thing off such a shelf, you have to check what you are holding.
.NET has such a shelf too: the old list ArrayList, which came before List<T>. It stores elements as object, a type that holds a value of any type. What happens if you add up two of its elements?
using System.Collections;
var items = new ArrayList();
items.Add(5);
items.Add(7);
int sum = items[0] + items[1]; // ✗error CS0019: Operator '+' cannot be applied to operands of type 'object' and 'object'The program does not compile. The list holds the numbers 5 and 7, but the compiler knows only that the elements of an ArrayList are object. And two object values cannot be added.
ArrayList lives in the namespace System.Collections, so the file starts with a using directive.
To add the elements, each one is cast to int: (int)items[0]. If a string turns out to be in that place, the error is found only when the program runs: it stops with an InvalidCastException.
The type in angle brackets
List<T> works differently. The letter T in its name is a type parameter: a slot for a type that is filled in when the list is declared. In List<int> the T is int, in List<string> it is string. A type with a type parameter is called generic, and the language feature itself is called generics.
List<int> nums = [5, 7];
List<string> names = ["Ann", "Bob"];
int sum = nums[0] + nums[1];
string first = names[0];
Console.WriteLine(sum); // 12
Console.WriteLine(first.Length); // 3Let's see what the compiler knows here:
- A
List<int>holdsintnumbers, sonums[0] + nums[1]adds up with no casts. - A
List<string>holds strings, sonames[0]is a string right away, and it hasLength. List<int>andList<string>come from one generic type,List<T>, but they are two different types, like two shelves with different labels.
Here the label picture is off. A label on a shelf can be replaced, but a list's element type is set once and for all: a List<int> never becomes a List<string>.
Why generics
Interviewers often ask why generics are needed when there is object. The answer has three parts:
- the compiler checks the element type: a string cannot go into a
List<int>, and the error shows before the program runs, not while it works; - no casts are needed: an element of a
List<int>is already anint; - the numbers sit in the internal array as they are, while
ArrayListwraps each number in a separateobject, and that takes time.
That is why new code does not use ArrayList: List<T> has taken its place.
A generic method of your own
Methods can have a type parameter too, not only types. A method with a type parameter is called a generic method. Here is a method Swap that swaps the values of two variables. Without generics it would have to be written twice, for int and for string, but with the parameter T one is enough:
int a = 1;
int b = 2;
Swap(ref a, ref b);
Console.WriteLine($"{a} {b}"); // 2 1
string x = "left";
string y = "right";
Swap(ref x, ref y);
Console.WriteLine($"{x} {y}"); // right left
void Swap<T>(ref T left, ref T right)
{
T tmp = left;
left = right;
right = tmp;
}Let's go through the declaration and the calls:
<T>after the method name declares a type parameter. From there on,Tis written wherever a type is needed: for the parameters and for the variabletmp.refhands the method the variables themselves, so the swap is still visible after the call.- In the call
Swap(ref a, ref b)the compiler works outTfrom the arguments by itself: hereTisint, and inSwap(ref x, ref y)it isstring. - The type can also be given explicitly:
Swap<int>(ref a, ref b).
Common mistake
Someone tried to put a string into a list of numbers:
List<int> nums = [1, 2];
nums.Add("three"); // ✗error CS1503: Argument 1: cannot convert from 'string' to 'int'The program does not compile: the Add method of a List<int> takes only an int. This is the very check generics were made for. An ArrayList would have taken the string with no error, and the program would have broken later, at the cast to int. To add a number, write nums.Add(3).
Key points
TinList<T>is a type parameter: inList<int>its place is taken byint, inList<string>bystring.- Generics give a type check at compile time and remove the casts: an element of a
List<int>is anintright away. ArrayListstores everything asobject: a wrong type shows up only when the program runs, so new code usesList<T>.- A generic method declares its own type parameter,
void Swap<T>(ref T left, ref T right), and the compiler works outTfrom the arguments.