Generics in C# allows you to write type safe, reusable and high-performance template-based program, it is a very handy feature and was introduced in C# 2.0.
In this article, we will learn about the basic concept of generics, its benefits and the usage.
What is generics in C# ?
Generics is a language feature, which allows us to design classes, methods, Interface and delegates without any specific data type. The data type information is passed to the generic type during declaration and instantiation time.
This enables in developing common classes, methods, Interface and delegates, which helps in the application performance, improves the productivity and provides the type safety feature.
This is what Microsoft says about generics.
“Generics are the most powerful feature of C# 2.0. Generics allow you to define type-safe data structures, without committing to actual data types. This results in a significant performance boost and higher quality code, because you get to reuse data processing algorithms without duplicating type-specific code”
Create a Generic method in C#:
A generic method with a type parameter specification <T> declared as follows:
1 2 3 4 |
public void MyGenericMethod<T>(T value) { Console.WriteLine(value.ToString()); } |
In above, we have declared a Generic method. Here, <T> stands for the type parameter, that will be provided during the method invocation time. Lets take below example as follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Program { static void Main(string[] args) { //Generic method call by passing string parameter MyGenericMethod<string>("Hello world"); //Generic method call by passing int parameter MyGenericMethod<int>(12345); Console.Read(); } //Generic method declared with T as parameter passed. public static void MyGenericMethod<T>(T value) { Console.WriteLine(value.ToString()); } } |
This generates below output.
Here in above,
- We are invoking the Generic method
MyGenericMethod<T>(T value) from the main method by specifying
string type and then
integer type.
-
12MyGenericMethod<string>("Hello world");MyGenericMethod<int>(12345);
-
- If we declare something like this – MyGenericMethod<string>(12345); we will get a compile time error, because we have specified string type but passing an integer parameter. However, this MyGenericMethod<int>(12345); will work fine.
So here the compiler does a type check and allows only the specified types and throws error if it finds any other type. This is what we say as type safety property of generics.
Generic Classes:
A classic example of an inbuilt generic class is a List<T>
, <T> is the data type. It allows you to create lists of various data types without sacrificing type safety.
1 2 |
List<int> intList = new List<int>(); List<string> strList = new List<string>(); |
Create a Custom Generic class in C# :
A generic class with a Type parameter specification <T> declared as follows.
1 2 3 4 5 6 7 |
public class MyGenericClass<T> { public void MySimpleMethod(T value) { Console.WriteLine(value.ToString()); } } |
In the above code sample.
- We have declared a Generic class. Here, <T> stands for the type parameter, the Generic class type can be explicitly specified during the class instantiation.
- Here, we haven’t specified the <T> before the method declaration. Its optional in this case, the same Generic class type parameter can be reused.
- We have also declared MySimpleMethod<T> as a generic method inside the generic class, we can invoke above Generic class by specifying different data types.
Lets take an example as follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Program { static void Main(string[] args) { Console.WriteLine("Output from generic class:"); MyGenericClass<String> myClassObj = new MyGenericClass<String>(); myClassObj.MySimpleMethod("Hello world"); MyGenericClass<int> myClassObj2 = new MyGenericClass<int>(); myClassObj2.MySimpleMethod(12345); Console.Read(); } } public class MyGenericClass<T> { public void MySimpleMethod(T value) { Console.WriteLine(value.ToString()); } } |
Herein above,
- We have declared
MyGenericClass of
<string> type and pass the string as method parameter.
-
12MyGenericClass<String> myClassObj = new MyGenericClass<String>();myClassObj.MySimpleMethod("Hello world");
-
- Again, from the Main method, we have declared
MyGenericClass of
<int> type and pass the int parameter.
-
12MyGenericClass<int> myClassObj2 = new MyGenericClass<int>();myClassObj2.MySimpleMethod(12345);
-
- In both cases the code works fine, since we have instantiated and also provided same type parameter to the Generic class method. However, we will get compile time error if we do something like this as shown below. Here type mismatch happens, and compiler throws compile time error.
-
12MyGenericClass<int> myClassObj2 = new MyGenericClass<int>();myClassObj2.MySimpleMethod("12345");
-
Benefits Of Generics:
Following are the benefits that Generics brings to our program.
- Generics are type safe.
- Generics provides code re-usability.
- Generic has a performance advantage over non-generics.
How Generics are type safe ?
Type safe means that the compiler performs a check on the types during compilation and throws error when it finds an incompatible data type.
In C#, ArrayList
class is not type-safe because it allows to store any object. For instance, If you look at below code example, It will compile without any error or warning. However during run-time, it will throw InvalidCastException.
1 2 3 4 5 6 7 8 9 10 |
ArrayList arrlstNumber = new ArrayList(); arrlstNumber.Add(10); arrlstNumber.Add(1.5); arrlstNumber.Add("Hello world"); int sum = 0; for (int x = 0; arrlstNumber.Count; x++) { int value = (int)arrlstNumber[x]; sum = sum + value; } |
Above code will compile fine because. ArrayList.Add(Object obj) method allows objects . In C#, all types are derived from System.Object. So we are able to add an integer (10), a float (1.5) and a string (“Hello world”) to the ArrayList arrlstNumber without any compile time error.

When we run the above program. It successfully casts the first variable which is of the type integer (10) from the array list and assigns to the temporary variable. However, it throws InvalidCastException while casting the second variable (1.5) which is of type float to integer.
Generics will be useful in this scenarios in-order to remove run-time errors and to ensure that errors are caught at compile time.
Lets check how Generics solves this issue ?
While using Generics, the compiler understands and determines the type and ensures that objects compatible with that specific type is used in the generic method or generic class. It throws compile time error when it finds an incompatible type.
Lets use a Generic type List<int> to the above example.
1 2 3 4 5 6 7 8 9 |
List<int> arrlstNumber = new List<int>(); arrlstNumber.Add(10); arrlstNumber.Add(1.5);//Compile error arrlstNumber.Add("Hello world");//Compile error int sum = 0; for (int x = 0; x < arrlstNumber.Count; x++) { sum = sum + arrlstNumber[x]; } |
As soon as the compiler sees the type <int> then it ensures that there will be only int variables in the List<int> arrlstNumber. So there is compile time type check which ensures any possible run time issue.
How generics provides code re-usability?
Generics allows us to design type independent function, class, interfaces, delegates, events etc. that can be reused across different types. Lets check below sample example on swapping of two number.
The above example runs fine for integer type variable only. If we try to swap two string variable, then the compiler throws a compile time error as shown below.
1 2 3 4 5 6 7 |
static void SwapNumbersGeneric<T>(ref T num1, ref T num2) { T tempNum = default(T); tempNum = num1; num1 = num2; num2 = tempNum; } |
Having a Generic method to swap two values removes the possibility of creating separate methods for each type. Look at below code example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
using System; namespace GenericSwappingExample { class Program { static void Main(string[] args) { string x = "100"; string y = "50"; Console.WriteLine("Before swapping string – x : {0} , y : {1}", x, y); SwapNumbersGeneric(ref x, ref y); Console.WriteLine("After swapping – x : {0} , y : {1}", x, y); int n1 = 1000; int n2 = 150; Console.WriteLine("Before swapping Integer – n1 : {0} , n2 : {1}", n1, n2); SwapNumbersGeneric(ref n1, ref n2); Console.WriteLine("fter swapping – n1 : {0} , 2 : {1}", n1,n2);decimal d1 = 10.125M; decimal d2 = 00.125M; Console.WriteLine("Before swapping decimal – d1 : {0} , d2 : {1}", d1, d2); SwapNumbersGeneric(ref d1, ref d2); Console.WriteLine("After swapping – d1 : {0} , d2 : {1}", d1, d2);Console.Read(); } static void SwapNumbersGeneric<T>(ref T num1, ref T num2) { T tempNum = default(T); tempNum = num1; num1 = num2; num2 = tempNum; } } } |
Output –
Continuing on this, In the next article, We explore at the performance benefits that Generics provides in a program comparatively to non Generic type. Click here to check it out.