6 min read

Generics

Crash Course

The Motivation for Generics

Arrays are great for storing collections of items, but have a rather serious limitation: you can’t change the size of an array after it has been created. That makes it hard to add and remove items from an array. If you want to grow an array, you must make a new array that is one item bigger, and copy everything over.

int[] numbers = new int[] { 1, 2, 3 };

// Grow the array and add another number:
int[] copy = new int[numbers.Length + 1]; // Slightly bigger.
for (int index = 0; index < numbers.Length; index++) // Copy everything over.
    copy[index] = numbers[index];
copy[copy.Length - 1] = 4; // Put the new value in at the very end.

numbers = copy; // Update `numbers` with the new, slightly longer array.

That’s kind of a pain, right?

We might get creative and say, “Well, if we made a class that does this for us, we could just reuse it over and over!” You’re on to something there!

class ListOfNumbers
{
    private int[] _numbers;

    public ListOfNumbers()
    {
        _numbers = new int[0]; // Start empty.
    }

    public void Add(int newNumber)
    {
        // Grow the array and add another number:
        int[] copy = new int[_numbers.Length + 1]; // Slightly bigger.
        for (int index = 0; index < _numbers.Length; index++) // Copy everything over.
            copy[index] = _numbers[index];
        copy[copy.Length - 1] = newNumber; // Put the new value in at the very end.

        _numbers = copy; // Update `_numbers` with the new, slightly longer array.
    }
}

Now we could use this class like this:

ListOfNumbers numbers = new ListOfNumbers();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);

That is a fantastic application of making a reusable class. An outstanding move.

But what if we need a similar thing for float or bool or string?

We could make other classes. ListOfString, ListOfFloat, ListOfBool. They’d look identical to ListOfNumbers except everywhere we see int in the ListOfNumbers class, it would say string, float, or bool.

That means manually creating a lot of very similar classes, which doesn’t sound like fun.

We saw that object is the base class of everything. So we could technically make a plain old List and use object throughout it. Then we could use it for literally any type we want.

There’s a problem, though:

List numbers = new List();
numbers.Add(1);
numbers.Add("two");

Because we’re using object, the above code is allowed. We could accidentally get the wrong type in there. To be safe, we’d always need to check the type and cast, every time we use it. When we had ListOfInt, we didn’t have that problem.

There’s got to be a better way, right? There is!

Generics to the Rescue

Generics are a mechanism that allow you to define a type with placeholders for types that can be filled in later, when they’re used.

Our List/ListOfNumbers becomes the following when we turn it into a generic class:

class List<T>
{
    private T[] _values;

    public List()
    {
        _values = new T[0]; // Start empty.
    }

    public void Add(T newNumber)
    {
        // Grow the array and add another number:
        T[] copy = new T[_numbers.Length + 1]; // Slightly bigger.
        for (int index = 0; index < _numbers.Length; index++) // Copy everything over.
            copy[index] = _numbers[index];
        copy[copy.Length - 1] = newNumber; // Put the new value in at the very end.

        _numbers = copy; // Update `_numbers` with the new, slightly longer array.
    }
}

On the top line where the class is defined, we have that <T> thing. That is a generic type parameter . It is a placeholder for another type, which will be determined later, when the class is used.

You can see usages of T throughout the class, including the type of the _values field and the type of the parameter for Add.

To use this type, we’d do the following:

List<int> numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);

List<string> words = new List<string>();
words.Add("Hello"):
words.Add("World");

Some Existing Generic Types

The List<T> Class

The List<T> class we made above seems like the beginning of something extremely useful, doesn’t it?

Well there is already a class like that already defined for you (including Add, but also Remove and indexing values, and a whole ton of other features). You don’t need to make your own. It already exists.

warning

In fact, other than seeing the example code above, you should not make your own List<T> class! It already exists, and the version that already exists handles so much for you, that it would be a waste to spend your time making your own when such a flexible, powerful type already exists for you.

Actually, with this List<T> class we’re about to discuss, you probably will only very rarely have a need to use an array. List<T> is better in virtually all situations.

To use the existing List<T> class, you may need to add the following using directive to the top of your file:

using System.Collections.Generic;

note

The above using directive has been necessary to use List<T> ever since it was first introduced. But with C# 10, that using directive will become automatic in nearly all projects. After November 2021, you will not typically need to add that yourself.

The following code shows some of the most important features of the List<T> class:

List<int> numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
Console.WriteLine(numbers.Count); // Notice that this is `Count` not `Length`.

numbers.Remove(2); // Removes the first item equal to this.
numbers.RemoveAt(0); // Zero-based indexing.

numbers.Add(3); // Put a few things back in after the previou removals.
numbers.Add(4);

Console.WriteLine(numbers[0]); // You can use square brackets to access items at a specific index.
Console.WriteLine(numbers.IndexOf(4));

The Dictionary<K, V> Class

Another extremely useful type is the Dictionary<K, V> class.

note

This class shows how you would add multiple generic type parameters, by placing multiple names in the angle brackets (< and >) and separated by commas.

This type is useful when you have one piece of information that you want to look up from another piece of information.

For example, imagine you have this Color enumeration:

enum Color { Red, Green, Blue, Yellow }

And you want to look up Player instances based on the color that is assigned to that color:

Dictionary<Color, Player> playerAssignedToColor = new Dictionary<Color, Player>();

Player player1 = new Player();
Player player2 = new Player();

playerAssignedToColor[Color.Red] = player1;
playerAssignedToColor[Color.Green] = player2;

Then whenever you want, you can find the player that belongs to a given color with:

Console.WriteLine(playerAssignedToColor[Color.Blue]);

We could look up a phone number from a name, look up a sprite or texture to draw for an object in a game based on the object’s type or name, etc. There really are a lot of uses for the Dictionary<K, V> class.

Generic Methods

In addition to generic types, you can also define generic methods. The concept is the same, but operates at the level of a single method instead of across an entire class:

public T Load<T>(string resource) { /* ... */ }

You will someday find a place where you want to create your own generic methods, but you’ll probably run into usages of generic methods much sooner. The example above actually comes from the MonoGame engine, which it uses to load content. It has a generic type parameter, T, and by calling Load<T>, you know the return type will also be of that type. For example:

Texture2D sprite = Content.Load<Texture2D>("ForestWorld/Sprites/Tree");
Song song = Content.Load<Song>("ForestWorld/Music/Background");

If this weren’t a generic method, you wouldn’t be able to make any guarantees about the types being used, and the best we could do is:

object sprite = Content.Load("ForestWorld/Sprites/Tree");