We use a lot of data types while programming in C#. They are used as class properties, parameters in methods, and more. But in some cases, you have a class that can be used for multiple data types. Instead of specifying a fixed data type for these constructs, you can use a placeholder type parameter that is filled with a specific data type when the class or method is used. And this is how C# generics work.
Table Of Contents
Goals
After this article you:
- Know what generics are and how to use them in C#
- Can create a simple generic class and method
- Know how generics work with different data types
- Are able to implement and use generics in c#
The Idea Behind C# Generics
Generics allows us to define classes and methods with placeholders, which are replaced by the C# Compiler with a specific type at compile time. This helps us save coding, duplicate code violations, and reuse code we already created.
C# When you see generics for the first time, you might end up looking like this:

Okay, real-life example for C# generics: If you look up to the sky, you can see a plane. But what kind of plane? A passenger plane or a cargo plane? Maybe it’s a small one-propeller plane or a full Boeing 777. What airline is it? And that’s exactly how generics work: We know it is something, but it could be anything. Most importantly, we know it’s a plane, but as long as we don’t look closer, we don’t know the specifics.
The Generic List
One of the biggest examples of a generic is the List. A list contains items with the same data type. Whether it is a string, integer, own class… It doesn’t matter; it always contains the same types. Here are a few examples:
List<int> numbers = new();
List<string> greetings = new();
public class Movie
{
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
}
List<Movie> movies = new();
The date type between the <> is the generic data type and it’s usually marked with a T.
But there are other reasons to use C# generics. Another reason is the duplicate code violation or DRY. This states that you can’t have the same code in different places. Therefore, you should try to generalize the code. If you use the code in different places with different data types, generics can help you.
C# Comparer
Let’s take a look at a comparer. Yes, there are different ways to fix this, but it’s a good example of C# generics.
Comparer comparer = new();
Console.WriteLine($"1 and 2 are the same: {comparer.AreEqual(1, 2)}");
Console.WriteLine($"1 and 1 are the same: {comparer.AreEqual(1, 1)}");
Console.WriteLine($"'abc' and 'def' are the same: {comparer.AreEqual("abc", "def")}");
Console.WriteLine($"'ghi' and 'ghi' are the same: {comparer.AreEqual("ghi", "ghi")}");
public class Comparer
{
public bool AreEqual(int a, int b)
{
return a.Equals(b);
}
public bool AreEqual(string a, string b)
{
return a.Equals(b);
}
public bool AreEqual(double a, double b)
{
return a.Equals(b);
}
}
The code does the exact same thing on lines 12, 17, and 22. There are two ways to fix this:
- Make the int a and int b parameters objects
- Implement generics
If we would use objects the data type of the parameters will get lost and the comparison isn’t valid anymore. Generics would preserve the data types.
Make It Generic
To make it generic, we must tell the method AreEqual what data type is expected. We also have to change the comparer.AreEqualso we tell the implemented method what types we are using. Here is the implementation:
Comparer comparer = new();
Console.WriteLine($"1 and 2 are the same: {comparer.AreEqual<int>(1, 2)}");
Console.WriteLine($"1 and 1 are the same: {comparer.AreEqual<int>(1, 1)}");
Console.WriteLine($"'abc' and 'def' are the same: {comparer.AreEqual<string>("abc", "def")}");
Console.WriteLine($"'ghi' and 'ghi' are the same: {comparer.AreEqual<string>("ghi", "ghi")}");
public class Comparer
{
public bool AreEqual<T>(T a, T b)
{
return a.Equals(b);
}
}
We went from 24 lines of code to 14, which is 10 less! That’s a win! Take a good look at line 10. The T defines the method as generic. Parameters a and b are generic too, meaning it has no idea what data types to expect. The first <T> specifies the Type Parameter and the T’s in the parameter list of the method use that Parameter Type.
Lines 3 to 6 are a bit different too. I have added a data type between the <> behind the AreEqual. This way the method in the class Comparer knows which data types it needs to compare.
Cleaning Up
Now, the compiler is smart enough to understand that if you use two integers or two strings for a and b it doesn’t have to tell the method which data type to use. We can remove the data type from the places where we call AreEqual:
Comparer comparer = new();
Console.WriteLine($"1 and 2 are the same: {comparer.AreEqual(1, 2)}");
Console.WriteLine($"1 and 1 are the same: {comparer.AreEqual(1, 1)}");
Console.WriteLine($"'abc' and 'def' are the same: {comparer.AreEqual("abc", "def")}");
Console.WriteLine($"'ghi' and 'ghi' are the same: {comparer.AreEqual("ghi", "ghi")}");
public class Comparer
{
public bool AreEqual<T>(T a, T b)
{
return a.Equals(b);
}
}
Generic And Different Data Types
But what happens if you use the previous example and call something like the code below?
comparer.AreEqual<int>("hello", 2);
This will create a compile error with the error:
Error CS1503 Argument 1: cannot convert from ‘string’ to ‘int’
So, isn’t it possible to use different data types in C# generics? Oh no, it is possible. And it’s really not that hard. Just define the generics:
SomeClass someClass = new();
someClass.WriteMessage("Hello world", 1245);
someClass.WriteMessage(10.4D, 1245);
public class SomeClass
{
public void WriteMessage<T1, T2> (T1 value1, T2 value2)
{
Console.WriteLine($"Value 1 = {value1} and value2 = {value2}");
}
}
You can define two generics in the same methods and use them.
C# Generic In Classes
The previous examples were all in methods, but you can make a class generic too. This will define the parameter type in the class, rather than the method.
SomeClass<string, string> someClass = new();
someClass.WriteMessage("Hello world", "This is a message");
SomeClass<int, string> someClass2 = new();
someClass2.WriteMessage(12345, "This is a message");
public class SomeClass<T1, T2>
{
public void WriteMessage(T1 value1, T2 value2)
{
Console.WriteLine($"Value 1 = {value1} and value2 = {value2}");
}
public void DebugMessage(T1 value1, T2 value2)
{
Debug.WriteLine($"Value 1 = {value1} and value2 = {value2}");
}
}
Now we don’t have to add the parameter types per method, but we add it once in the class and the methods can use it. The downside is that we need to initialize the class each time we want to use different types, as you can see on lines 1 and 4.
Specific Data Type
We can also specify that only a specific data type may be used. Let’s assume we have a class for movies:
public class Movie
{
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
}
Then we create a new class with the name Shrek:
public class Shrek : Movie
{
public Shrek()
{
Title = "Shrek";
ReleaseDate = new DateTime(2001, 7, 12);
}
public void MakeSound()
{
Console.WriteLine("ROARRRR!");
}
}
Next is a class called GenericMovie:
public class GenericMovie<T> where T : Movie
{
private readonly T movie;
public GenericMovie(T movie)
{
this.movie=movie;
}
public void GiveInformation()
{
Console.WriteLine($"The movie {movie.Title} has been released on {movie.ReleaseDate}");
}
}
The console application looks like this:
Shrek shrek = new(); GenericMovie<Shrek> sc = new(shrek); sc.GiveInformation(); shrek.MakeSound();
The GenericMovie class handles some functionality for all objects that are of the type Movie. Therefore, the class knows about the properties Title and ReleaseDate.
In a lot of examples, you’ll see where T : class, meaning that everything that is of the type class is accepted in the generic parameter type. You will have no idea about the properties of that class.
Conclusion
Well, there you have C# generics. It’s a great way to create reusable code and make your code easier to read. One of the reasons I use generics is the repository pattern. The parameter type is a class and I can send all kinds of classes to this generic class, which will handle all the communication with the Entity Framework data context.
C# generics need some practice before you can fully grasp them. It took me a while to understand and implement it. But it really helped me create better code. Not only for myself but also for my teammates.

