It’s hard to do much development in dotnet without running into generics. After all, since .NET Framework 2.0 in the early 2000’s we’ve had List<T>
and Dictionary<TKey, TValue>
. But what exactly are generics in C#?
In this article we’ll explore what generics are in C#, how they work, how we already take advantage of them in .NET code, and how implementing your own generic types and generic methods in C# can help level up your coding ability.
What are Generics in C#?
Generics are a dotnet language feature built to reduce the amount of copying and pasting that we as developers might need to do to provide generic functionality that can apply to any dotnet Type that wants it.
The World before C# Generics
For example, let’s say that you needed to make a method that takes in a list of integers and returns an array of just the first element and the last element of the list (assuming lists will not be null and will have at least one element in them).
The code for that would be something like the following:
public int[] BuildFirstLastArray(List<int> input)
{
int[] result = new int[2]();
// For ease of readability, let's just use LINQ here
result[0] = input.First();
result[1] = input.Last();
return result;
}
Admittedly this is an arbitrary set of requirements and some fairly simple code.
Let’s say that our original method is meeting our needs, but now we get a new requirement that says we need to be able to return an array of the first and last element in a list of strings.
Our code for this is remarkable similar to our code for the integer list above:
public string[] BuildFirstLastArray(List<string> input)
{
string[] result = new string[2]();
// For ease of readability, let's just use LINQ here
result[0] = input.First();
result[1] = input.Last();
return result;
}
If you’re anything like me, you might now start to wonder if there’s a better way. After all, your code is likely not as trivial as this sample problem and we’ve all learned the hard way that duplication is bad.
As you might expect from the subject of this article, generics can help with this problem.
Declaring a Generic Method in C#
Using C# generics, we can create a method that effectively acts as a template for the methods we have explicitly declared above.
That’s a complex sentence, so let’s get into an example.
In our code earlier, the only difference between the two methods was that one of the methods worked with int values and the other worked with string values.
Instead of declaring a new method per type that we wanted to support, we could instead use a placeholder or generic type in your code to represent a type that will be provided later.
We do this by using angle brackets (<>
‘s) after the method name and before the parentheses with parameter declarations as shown below:
public T[] BuildFirstLastArray<T>(List<T> input)
{
T[] result = new T[2]();
// For ease of readability, let's just use LINQ here
result[0] = input.First();
result[1] = input.Last();
return result;
}
Admittedly, the code above looks stranger without the explicit type declarations and the added <T>
before the parameter list, but this generic method can now handle being called for lists of string, int, double, or a variety of other types.
Calling a Generic Method in C#
We call a generic method by telling the C# compiler what value we want to be provided for the generic type parameters.
For our example above, we could call BuildFirstLastArray
for int
s and string
s like the following:
List<int> numbers = new() { 1, 2, 3};
int[] intResults = BuildFirstLastArray<int>(numbers);
List<string> names = new() { "Priya", "Mohammed", "Alexis" };
string[] strResults = BuildFirstLastArray<string>(names);
Note how in the code above we provide the <int>
and <string>
generic type parameters to the method above. When this code runs, the int or string values are substituted for the T placeholder in the generic method.
It’s also possible to have a method that takes in multiple generic type parameters. We can support this by adding a comma and then subsequent type parameters after that inside the angle brackets like we see below:
public string CombineTypes<T1, T2>(T1 firstType, T2 secondType)
{
// Implementation omitted...
}
Here we have a CombineTypes
method that can be called with any two types (including the same type twice). These type parameters will be used inside of the method and called T1 and T2 respectively.
Note: you don’t have to call your generic type parameters T
, T1
, or T2
but it is generally the convention to use T
when providing a single type parameter. When providing multiple type parameters it is usually better to use a meaningful set of names such as TKey
and TValue
, but if this is not possible T1
, T2
, etc. can still help clarify usage.
What the C# Compiler does with Generics
These Type parameters cause C# to automatically generate a method for the Type parameter(s) specified each time the compiler encounters a different combination of type parameters.
The main difference between manually creating all of these overloaded methods with different types and using generics is that we don’t need to repeat our code if we use C# generics. This lets us easily add new features, documentation, or fix issues with our code in one place and have that change be effective for any version of that generic method.
Generic Classes in C#
We’ve seen how we can create generic methods, but sometimes you want entire generic classes centered around a generic type. List<T>
is a great example of this. The generic List manages ordered collections of a specific type of item and provides strongly-typed methods and properties for interacting with that collection.
Let’s take a look at how you can declare and use generic classes of your own in C#.
Declaring a Generic Class in C#
In a generic class the type parameters are associated with the Type itself, not with individual methods. These type parameters can then be used inside of methods and properties as you’d like.
Here’s a simple generic class that tracks the latest item you provide to it:
public class LastItemTracker<T>
{
public T LastItem {get; private set;}
public void TrackItem(T item)
{
LastItem = item;
}
public bool IsLastItem(T item)
{
return LastItem == item;
}
}
This is a silly class that just has a few methods and a single property. You can call TrackItem
to have it track a specific item of the class’s generic Type parameter. LastItem
will then expose that value and the IsLastItem
method can be called to check if another item is equal to the LastItem
the class is tracking.
While you’d never use a class quite this simple in the real world, this should illustrate that the same generic type parameter for the class can be reused in methods and properties within that class.
Using a Generic Class in C#
If you’ve used a List<T>
before, you’re already familiar with instantiating and using generic classes in C#.
We provide the generic type parameter to the class when the class is declared.
For example, the code below creates a new instance of our LastItemTracker
class that will manage string instances using the target-typed new keyword:
LastItemTracker<string> stringTracker = new();
stringTracker.TrackItem("Hello");
stringTracker.TrackItem("World");
Console.WriteLine(stringTracker.LastItem); // Prints out "World"
bool isLastItem = stringTracker.IsLastItem("Hello"); // false
That’s it. Once you declare a generic class by providing the generic type arguments, the various methods and properties that rely on that generic type will automatically be updated to use that type. What’s more, the compiler and IntelliSense will reflect this, giving you a safe and reliable development experience.
When you declare a generic class in C#, the C# compiler will actually automatically generate a new class for you each unique set of generic type parameters you use when declaring that class.
When should you use C# Generics?
So, when should you declare a method or class as generic?
I declare classes and methods as generic when I find myself needing two very similar methods or classes and interfaces or inheritance are not appropriate for the type of problem I’m trying to solve.
Personally, I find myself using generic classes much more frequently than stand-alone generic methods. However even generic classes are a rarity for me to use. I’ve seen some success with generics in declaring common result objects for APIs, nodes in linked lists or neural networks, or even custom exceptions.
In practice, you’ll likely declare your own generics infrequently, but understanding how you can create your own generics goes a long way towards increased comfort and mastery of the existing generic classes in dotnet.
If you have a specific use-case for generic classes or methods that you’d like to share, let me know! I’m always curious to see what creative solutions people use language features for.
Top comments (5)
Good and clean explanation! Congrats ;)
Secret tip: You can add the # at the end of a title when using C# if you do it this way:
## Declaring a Generic Method in C# #
So the result will be:
Declaring a Generic Method in C#
Looking forward to your new articles!😁
Fixed. Thank you
Great introduction to generics, I would love a second part tackling the deeper level of it, regarding covariance, contravariance and whatnot !
Not a bad idea. I'll add it to my backlog.
I love generics, have a look how to create a Genric Method with Generic Parameters
dev.to/frederik_vl/generic-methods...