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

C# Generics for Beginners - Part 1

0.00/5 (No votes)
12 Jun 2017 3  
A tutorial about generics in C#, aimed at beginners

Article Index

C# Generics for Beginners - Part 1

C# Generics for Beginners - Part 2

Introduction

When I was still a C# rookie, I remember being intimidated by generics. If I was capable of time travel, this is the article I would like to send back in time to myself. In other words, this article is aimed at beginners, and hopefully it demystifies generics for a lot of people starting out with C# out there.

I am going to start extremely simple, and slowly ramp up the complexity.

So let's not waste any time on useless babbling. Let's jump right in.

First Steps

Here is an extremely simple method. It returns one integer.

public int ReturnsOneInt()
{
    return 1;
}

But let's say that we want to return more than one integer from this method. Methods can only return one thing at a time, so if we wanted to return two integers we will have to change it like this:

public class TwoInts
{
    public int Int1 { get; set; }
    public int Int2 { get; set; }
}
public TwoInts ReturnsTwoInts()
{
    return new TwoInts() {Int1 = 1, Int2 = 2};
}

By simply packaging the two integer values into an object instance, we can return as many values as we want. But let's say we also need to return an integer and a string from yet another method? We will have to create a class like this now:

public class IntAndString
{
    public int Int1 { get; set; }
    public string String1 { get; set; }
}

Pretty soon, your code will be littered with these small classes. There is a better way. .NET has a generic class built in called Tuple.

You would use it like this:

public Tuple<int,int> ReturnsTwoInts()
{
    return new Tuple<int, int>(1,2);
}

Or:

public Tuple<int,string> ReturnsIntAndString()
{
    return new Tuple<int, string>(1,"two");
}

This is why generics are useful. Instead of creating a class for every specific scenario, we create a generic class, that can be used in many scenarios.

Our Own Tuples

To demonstrate what is going on here, we will create our own tuple class.

The code looks like this:

public class MyTuple<TypeParameter1, TypeParameter2> 
{
    public TypeParameter1 Value1 { get; set; } 
    public TypeParameter2 Value2 { get; set; } 

    public MyTuple(TypeParameter1 value1, TypeParameter2 value2)
    {
        Value2 = value2; 
        Value1 = value1; 
    }
}

On the first line, you will see the class declaration. The only thing new here are the two type parameters. Type parameters are parameters you have to pass to the generic class, much like parameters you pass to a method. But instead of passing the parameters in round brackets (), we pass them in <>.

You pass type parameters into the generic class like so:

MyTuple<int,int> twoInts = new MyTuple<int,int>(1,2);

Here, we pass two int types as the type parameters, so both TypeParameter1 and TypeParameter2 will be int.

Now look at the constructor on line 4. The same type parameters are used to define the types the constructor is expecting, which is why we are allowed to send in the values 1 and 2 into the constructor. The two public properties also use the type parameters to define their types, which allows us to set the parameter values to the parameters we receive in the constructor.

That is also why we can now do this:

int integerValue = twoInts.Value1;
integerValue = twoInts.Value2;

It is important to understand that the type of twoInts is MyTuple<int, int>. This can be demonstrated by doing this:

MyTuple<int, int> anotherTwoInts = twoInts; //COMPILES!

The next example will not compile since you are not allowed to set a variable to another variable if their types are not the same (note the second type variable is string):

MyTuple<int, string> anotherTwoInts = twoInts; //DOES NOT COMPILE!

Swapping the Values

Let's add some functionality to our tuple class.

public class MyTuple<TypeParameter1, TypeParameter2>
{
    public TypeParameter1 Value1 { get; set; }
    public TypeParameter2 Value2 { get; set; }

    public MyTuple(TypeParameter1 value1, TypeParameter2 value2)
    {
        Value2 = value2;
        Value1 = value1;
    }
    //notice that the type parameters of the return type has been swapped.
    public MyTuple<TypeParameter2, TypeParameter1> SwapValues()
    {
        //create a new instance if MyTuple, again with type parameters swapped to that it matches
        //the return type of the method itself
        return new MyTuple<TypeParameter2, TypeParameter1>(Value2, Value1);
    }
}

We have now added the SwapValues() method.

All it does is return a new instance of a MyTuple class, with the two values, as well as the positions of the type parameters, swapped. The important thing to note here is that we are using the type parameters we sent through when creating the instance of the tuple to define the return type of the SwapValues method.

Again, it is very important to understand how the types of these generic class instances play a role.

This might not seem important, but before you continue, make sure you understand why this does NOT compile:

MyTuple<int, string> swapped = intAndString.SwapValues(); //does NOT compile

And this DOES compile:

MyTuple<int, string> swappedAgain = intAndString.SwapValues().SwapValues();

Another interesting thing is that we are able to create an instance of a generic class, inside another generic class. This works, because we can pass the type parameters we receive on to other generic classes.

For instance, we can add this method to the MyTuple class:

public List<TypeParameter1> GetListOfValue1(int length)
{
    //create the list to return
    List<TypeParameter1> toReturn = new List<TypeParameter1>();
    //add Value1 to the list the required amount of times
    for (int i = 0; i < length; i++) toReturn.Add(Value1);
    return toReturn;
}

This new method simply creates a List, and populates it with a certain amount of Value1, and returns the list.

List<T>, just like our own MyTuple<T1, T2>, is a generic class, and so we send through type parameters. In the GetListOfValue1() method, we are creating a new instance of List<T>, and we send through TypeParameter1 as the type parameter required by List<T>.

Here is a more complex example:

public static List<MyTuple<T1, T2>> CombineLists<T1, T2>(List<T1> lst1, List<T2> lst2)
{
    List<MyTuple<T1, T2>> toReturn = new List<MyTuple<T1, T2>>();
    for (int i = 0; i < lst1.Count(); i++)
    {
        toReturn.Add(new MyTuple<T1, T2>(lst1[i], lst2[i]));
    }
    return toReturn;
}

This method accepts two lists as parameters, and then combines the items in the list into a single list of tuples. Note that the lists must be equal length, otherwise this will break.

The type parameters are defined here: CombineLists<T1,T2>. The two type parameters T1, and T2 are created, and used in the rest of the method.

The type parameters defined above are used to define the return type of the CombineLists method, which returns a list of MyTuple objects, which in turn receive T1 and T2 as their type parameters.

The CombineLists method takes two parameters. These are two lists. The first list uses type parameter T1 as its type, and the second T2.

Conclusion

This should be a good introduction to the basics of generics. There is still a lot of ground to cover, which I plan to do in future articles.

 

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