Generics

Generics

The Crash Course

  • You can create your own class that uses generics by placing the generic type in angle brackets after the class definition: public class PracticeList<T> { /* … */ }.
  • While you're not required, single capital letters for generic types is common, so T is a very common choice for a generic type.
  • You can have multiple generic types: public class PracticeDictionary<K, V> { /* … */ }.
  • With a generic type defined, you can use that type anywhere throughout your class, to declare other types, or as the return type of a method, or as the type of a parameter for a method. public T GetItem(int index) { /* … */ }
  • You can put constraints on your generic types, which is a way of saying "you can use any type you want, as long as it implements this interface, is derived from this class, or has these constructors". When you do this, then those constructors, or the methods the interface or base class has can be used throughout the class, because you know that the type, even though it is generic, will have those things.

Introduction

In the previous tutorial, we took a look at how generics work, in terms of how to use a class that uses generics. In this tutorial, we'll look at how to make your own classes that use generics.

In this tutorial, we're going to create our own version of the List class. I know, it already exists, and we already talked about it in the previous tutorial, but I think there's a need to keep things as simple as possible, and making a List class is going to be as simple as it gets. Generics, though, can be used in a very wide variety of places, so don't feel like all generics are good for is lists.

Additionally, it is worth pointing out that while we're creating our own List class for the sake of simplicity in this tutorial, you should typically use the standard List class, instead of this version, or instead of making your own. This is because the standard List class has essentially all of the features and methods that you'd want out of a list, and they are all well-tested, and are known to work. You shouldn't recreate code that has already been written if you don't have to.

To keep our own example list looking as different as possible from the standard List class, I'm going to call our list PracticeList, instead of just List.

Creating Your Own Generic Classes

Creating a class that uses generics is very similar to creating a plain old class. So you can start by adding a new class file (.cs file) to your project. I've called mine PracticeList (in PracticeList.cs), like I said, to keep our little example from looking too similar to the tried-and-true standard List class.

On the line where you declare your class, you will also put the angle brackets ('<' and '>') with the name that you are going to give to your generic type (T in this case), as shown below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Generics
{
    public class PracticeList<T>
    {
    }
}

You can see that this, so far, is very much the exact same as we'd see with a normal class, with the exception of having that <T> in there. This is what makes the class use generics.

Now we have a T type, that is currently undetermined—it could be anything. It is our generic type. We can now use this generic T type anywhere we want to, throughout our class, to represent the generic type.

Note, though, that we could put multiple types in the angle brackets, separated by commas, to have multiple generic types in our class. For example, we could use <K, V> for a key/value pair, just like the Dictionary class has.

By the way, there's nothing special about the T name, it is just very common, so I'm following suit here. The K and V are also very common. In general, people tend to use single letters for their generic types. It helps make it obvious that something is referring to a generic type, rather than just a class called T (since most classes have longer names than a single letter). But you could, technically call your generic type just about anything.

Using Your Generic Type in Your Class

Now that we've created a class with a generic type, we can use this type throughout our class. For instance, since we're trying to create a list, we probably want something to put the items of the list in. So we can add the following as an instance variable:

private T[] items;

We create a private array called items, but you'll see that the type of that array is T—our generic type. So whenever someone uses our PracticeList class, they'll choose what type to use, and the type they choose will go in here. If they create a PracticeList of ints (PracticeList<int> list = new PracticeList<int>();) then this will be an array of ints (private int[] items).

Let's keep building our class.

We probably also want a constructor that will set up our array to have 0 items in it to start:

public PracticeList()
{
    items = new T[0];
}

This should be simple enough. We create a new array with our generic type, with 0 items in it. (Yeah, that makes it kind of worthless, but it is the starting point, and we'll make the array longer when new items are added.)

We can also create a method that returns a particular item from the list. In this method, we'll return the generic type:

public T GetItem(int index)
{
    return items[index];
}

Again, this should be pretty straightforward. We simply return the generic type. If someone used it and made a PracticeList of ints, this would be returning ints. In fact, once you actually create the list, the IntelliSense in Visual C# Express will simply indicate that this method returns the int type.

We'll add one more method that adds items to the list. From a generics standpoint, this should be as simple as anything else that we've done here, though the code to actually add an item to our list is a bit more complicated:

public void Add(T newItem)
{
    T[] newItems = new T[items.Length + 1];
 
    for (int index = 0; index < items.Length; index++)
    {
        newItems[index] = items[index];
    }
 
    newItems[newItems.Length - 1] = newItem;
 
    items = newItems;
}

Here, our parameter is the generic T type that we defined for the class, which works like we've seen in the other pieces of the code.

To actually add the item to our array, well… we have a small problem. Arrays can't grow in size. Once you create them, that's the size they are forever. So basically, what we're going to do is create a new array that is one bigger than the original array, copy all of the items over to the new array, and then, in the new slot, we'll put the new item. Finally, we'll tell our instance variable (items) that is keeping track of the original array to now start looking at the new array that we just created. The code above does just this.

So to wrap this up, our completed class will look something like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Generics
{
    public class PracticeList<T>
    {
        private T[] items;
 
        public PracticeList()
        {
            items = new T[0];
        }
 
        public T GetItem(int index)
        {
            return items[index];
        }
 
        public void Add(T newItem)
        {
            T[] newItems = new T[items.Length + 1];
 
            for (int index = 0; index < items.Length; index++)
            {
                newItems[index] = items[index];
            }
 
            newItems[newItems.Length - 1] = newItem;
 
            items = newItems;
        }
    }
}

Generic Constraints

I don't want to go into a whole lot of detail on this, but it is worth mentioning that with generic types, you can indicate that they must be derived from a certain base class, or that they must implement a certain interface. To do this, you use the where keyword and the colon (':') operator:

public class PracticeList<T> where T : IComparable
{ 
   //...
}

Now, since we know that T must use the IComparable interface, we can use the methods of the IComparable interface anywhere we want to throughout the class.

If you have multiple generic types, you can specify type constraints for each of them using a new where keyword:

public class PracticeDictionary<K, V> where K : SomeRandomInterface 
                                                   where V : SomeOtherInterface
{ 
   //...
}

You can also specify constructor constraints, meaning that you can say "you can only use this generic list if the type you are using has a constructor with these parameters." So for instance, to indicate that the type must have a constructor with no parameters, you'd do this:

public class PracticeList<T> where T : new()
{ 
   //...
}

With this, we know that the T type has a constructor with no parameters, so anywhere in our class that it is needed, we could say:

T newObject = new T();

This would defer back to the generic T class's constructor with no parameters, which now, we can be sure it has, because we required it.

What's Next?

As we've seen in the last two tutorials, generics are very powerful, and can greatly reduce the amount of code that we need to write. There are many times that we'll be able to put generics to use, by using generic classes that someone else has already created, like the List and Dictionary classes, but also, by making our own generic classes as we have a need for it.

Up next, we'll start to take a look at more intermediate programming topics (going beyond the basics) and talk about the first of sort of a random collection of useful topics: reading from and writing to files.