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;
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;
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;
}
public MyTuple<TypeParameter2, TypeParameter1> SwapValues()
{
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();
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)
{
List<TypeParameter1> toReturn = new List<TypeParameter1>();
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.