Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Demystifying Generics in C#

0.00/5 (No votes)
25 Jan 2018 1  
Demystifying generics in C#

Generics have been added to the .NET Framework since version 2.0. They introduce the type parameter T to methods, classes (or types) and interfaces.

In programming, generics are also known as parametrized types or parametric polymorphism.

The concept of generics is not new. It has been there since 1973 when it was first pioneered by a programming language called ML, and throughout the years, generics were part of many programming languages including ADA, Java, Delphi, Rust and Huskell. In C++, generics are known as templates, and the STL (Standard Template Library) relies heavily on generics. For further and in-depth details regarding generics in programming languages, Wikipedia has a good and in-depth read about generic programming.

And also, there is a documented conversation about Generics in C#, Java and C++ with Anders Hejlsberg (the original author of Turbo Pascal, the chief architect of Delphi and the current lead architect of C#). I highly recommend you read this conversation.

With using generics, your code will become reusable, type safe (and strongly-typed) and will have a better performance at run-time since, when used properly, there will be zero cost for type casting or boxing/unboxing which in result will not introduce type conversion errors at runtime. The compiler of C# will be able to detect any problems of invalid casting at compile time.

Reusability with generics means that you will be able to create a method or a class that can be reused with different types in several places.

Now with generics, you will be writing only one method, that accepts parameter of type T, and inside this method, it will do the needed implementation, and it can either return void, or a concrete value (String, int, Book, …etc.) or even T itself.

Type safety and better performance both come together because at runtime, there will be no casting from type to type, it will be handled naturally.

Now, this is true that you will have a code reusability with having a single method that accepts an object type, and the Type you are passing, but you will be paying the cost of boxing/unboxing.

Boxing / Unboxing

Boxing is the process of converting a type to object (or as the name suggests, wrapping it inside the object container.

Unboxing means converting the object to a type or unwrapping the type from the object container.

Boxing/Unboxing are costly operations, and it is always better to not rely on them heavily in your code.

With Generics, you avoid doing boxing/unboxing since you are dealing with the parameter type T, which is a natural parameter that the compiler will replace with the concrete type at compilation time without doing the boxing operation at runtime.

By the way, if you would ask why we always use the letter T, it is because by convention, it refers to the word Type. You can use whatever valid letter or word you like for generics. The naming rules are no different than for naming classes.

And a last note, you are not tied to only use one parameter, you can specify multiple parameters separating them with comma , like <T, L, K> or whatever valid naming you like and then you can use the parameter in the place you want to use it.

Now let’s start with explaining more about generics with methods, classes, interfaces, with different examples.

Generic Methods

So our first example will be the generic method, let’s take a look at the following method that accepts 2 parameters of type object and compares the value of the 2 objects.

public static bool Compare(object value, object value2)
{
    return value.Equals(value2);
}

Now this might serve well in terms of code reusability with the use of boxing/unboxing feature with object types. However, you will risk getting runtime errors due to unsafe casting and there will be a cost for boxing/unboxing.

Now we will rewrite the method above to make it generic, see below:

public static bool Compare<T>(T value, T value2)
{
    return value.Equals(value2);
}

The above method is the simplest implementation for generics method. I just wanted you to understand the structure of method when it has the parameter T.

Now to call the above generic method, you will do the normal method call specifying the 2 arguments, the JIT (just-in-time) compiler will tell the type of the passed arguments and will deal with them naturally as if you are passing the concrete types.

Check the below sample Assert calls:

Assert.IsTrue(GenericsMethods.Compare(4, 4));
Assert.IsTrue(GenericsMethods.Compare("abc", "abc"));
Assert.IsTrue(GenericsMethods.Compare(5.4d, 5.4d));
Assert.IsTrue(GenericsMethods.Compare(2.3m, 2.3m));
Assert.IsTrue(GenericsMethods.Compare(7f, 7f));

Generic Classes

Now we will be creating a generic class. You create it the same way you create a normal class, but you just add the parameter T at the end of the class name. You can define the parameter T everywhere inside the class, with members and methods.

class Lesson<T>
{
    private T t;

    public void Set(T t){
        this.t = t;
    }
    public T Get()
    {
        return t;
    }
}

Now to call the above class with the concrete type, you will need to initialize the object with the tags < > , and inside it, you should specify the concrete type, you want to create your Lesson class with:

[TestMethod]
public void TestGenericClass()
{
    Lesson<int> integerLesson = new Lesson<int>();
    integerLesson.Set(1);
    Assert.AreEqual(1, integerLesson.Get());

    Lesson<string> stringLesson = new Lesson<string>();
    stringLesson.Set("C# Generics");
    Assert.AreEqual("C# Generics",stringLesson.Get());
}

Can we have a generic constructor? No, generic constructors are not allowed. Which means that you cannot define the parameter T on the constructor itself.

Read Jon Skeet’s answer on stackoverflow regarding generic constructors.

You can still use a factory creator static method and define to it the parameter T, but still I don’t think you really need that in most of the cases.

Generics Constraints

Constraints are like rules or instructions to define how to interact with a generic class or method. They can restrict the parameter that will be replaced with T to a certain type or class or have some properties, like to be a new instance of class.

If the consumer of the generic method or class didn’t comply with the constraint set on the parameter T, it will result in a compilation error.

With constraints, you can for example, specify a generic parameter to only accept reference types (class), or value types (struct), or to be a class that implements an interface.

Constraints can be defined using the keyword where.

Let’s see the below example that includes having a constraint on the above Lesson class, we will put a constraint that the parameter T should only be of a reference type, value types are not allowed, so it will be defined as where T: class.

class Lesson<T> where T: class
{
    private T t;

    public void Set(T t){
        this.t = t;
    }
    public T Get()
    {
        return t;
    }
}

Now if you try to intialize the above Lesson class using a non-reference type as its parameter, like int, you will get a compilation error:

The type ‘int’ must be a reference type in order to use it as parameter ‘T’ in the generic type or method ‘Lesson’

You can only call it with a reference type, like string or any class:

Lesson<string> stringLesson = new Lesson<string>();

You can define a constraint that a parameter must be implementing some interface. Let’s take this example:

class Container<T> where T: IItem
{
    private T t;

    public void Set(T t)
    {
        this.t = t;
    }
    public T Get()
    {
        return t;
    }
}

Here you have a class Container, that defines a generic parameter T, with a constraint that anyone wants to use this class, must pass an argument that implements the IItem Interface.

So here we added the IItem Interface, that has a method showName():

interface IItem
{
    string showName();
}

We also added a class Item, that will implement IItem Interface:

class Item : IItem
{
    private string name;

    public Item(String name)
    {
        this.name = name;
    }

    public string showName()
    {
        return name;
    }
}

Now in the caller part, what will happen is that we will initialize Item object, and then initialize Container while providing it and Item Type, and then calling the set method of container to set the item that was initialized. Check the below:

[TestMethod]
public void TestItemClass()
{
    Item pencil = new Item("Pencil");

    Container<Item> container = new Container<Item>();
    container.Set(pencil);
    Assert.AreEqual(pencil, container.Get());
    Assert.AreEqual("Pencil", container.Get().showName());

    Item pen = new Item("Pen");
    Container<Item> container2 = new Container<Item>();
    container2.Set(pen);

    Assert.AreEqual(pen, container2.Get());
    Assert.AreEqual("Pen", container2.Get().showName());
}

Generic Interfaces

This is similar to generic classes, you define a parameter T on the interface level, and your methods can use this parameter in their prototype, so any class that will be implementing this interface will naturally implement the parameter T within its own methods. You can also add constraints to generic interfaces. Note that the class that will be implementing the generic interface must define parameter T.

The example below will illustrate the use of a generic interface:

interface IBook<T>
{
    void add(T book);

    void delete();

    T get();
}

And here is the code for the implementer Book:

class Book<T> : IBook<T>
{
    T book;
    public void add(T book)
    {
        this.book = book;
    }

    public void delete()
    {
        this.book = default(T);
    }

    public T get()
    {
        return this.book;
    }
}

And to test the above implementation, we will be creating an object from Book with parameter String, and then we will use the add method to add the string value to the Book class. See below:

[TestMethod]
public void TestIBookInterface()
{
    Book<string> stringBook = new Book<string>();
    stringBook.add("Clean Code");
    Assert.AreEqual("Clean Code", stringBook.get());
}

System.Collections and System.Collections.Generic namespaces

In .NET Framework, the collections namespace includes classes that represent collection of objects that include lists, queues, bit arrays, hash tables and dictionaries.

The namespace System.Collections.Generic is the generic version of System.Collection namespace. Using it, you can define strongly typed collections while gaining all benefits of generics (code reusability, type safety and better performance).

Let’s take the most commonly used Collection type which is the List. I will not be explaining about Collections now because it is outside the scope of this article.

So with generics, the List will have its parameter T, that you can pass to it whatever type you like and you will have a list of that type.

Check the below example, we are defining a list of float points:

[TestMethod]
public void TestGenericList()
{
    List<float> points = new List<float>();
    points.Add(1.3f);
    points.Add(2.6f);
    points.Add(99.8f);
    points.Add(34f);

    Assert.AreEqual(34f, points[3]);
}

Conclusion

Generics are great abstraction and extensiblity feature that provides the benefit of code reuse, type-safety and performance gain. You just need to know where you should use it and the places where you can refactor your code to make use of generics.

I hope that I was able to demystify the important topic in C# for you. I tried to cover most aspects of generics in C#. There might be other details regarding each section I discussed above, just let me know if you have any questions or comments, or if you are having any issue using generics in your application.

To run the above sample codes inside an interactive in-browser widgets, I have ported this article along with its source code under tech.io platform. Please take a look at it and feel free to share and like the article here or there.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here