Table of contents
1.
Introduction
2.
What is Generic in C#?
3.
Syntax of Generics in C#
3.1.
1. Generic Class Syntax:
3.2.
2. Generic Method Syntax:
3.3.
3. Generic Interface Syntax:
4.
Generics in C# with example
4.1.
C#
4.2.
C#
4.3.
Solutions To The Above Problem
5.
Object Data Type
5.1.
C#
6.
Generics
6.1.
Generic Method
6.2.
C#
6.3.
Creating a Generic Class
6.4.
C#
7.
Features of Generics
7.1.
Type Safety
7.2.
Code Reusability 
7.3.
Enhanced Performance
8.
Advantages of C# Generics
9.
Frequently Asked Questions
9.1.
When should I use generics in C#?
9.2.
What is generics in C# Unity?
9.3.
Why are generics used?
9.4.
How can we make a method generic?
9.5.
How do generics improve the performance of the program?
10.
Conclusion
Last Updated: Nov 7, 2024
Easy

Introduction to C# Generics

Author ANKIT KUMAR
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

Generics in C# are powerful for creating reusable, type-safe code. By allowing developers to define classes, methods, and interfaces with placeholder types, generics enable the creation of flexible data structures and algorithms that work with any data type. Unlike traditional programming approaches where types are defined explicitly, generics allow for the deferral of specifying data types until the code is actually used, increasing code reusability and type safety. In this blog, we will explore the basics of C# generics.

Introduction to C# Generics

What is Generic in C#?

In C#, generics allow you to define classes, methods, and interfaces with placeholders for data types, which are specified later when the code is used. This enables you to write more flexible, reusable, and type-safe code without sacrificing performance or security.

Generics provide a way to define a type at runtime, while still maintaining compile-time type safety. With generics, you can create data structures and algorithms that can work with any data type without losing the benefits of type checking.

For example, a generic List<T> class in C# allows you to create lists that can store any data type (e.g., List<int>, List<string>, etc.), while maintaining type safety.

Syntax of Generics in C#

The basic syntax for defining generics in C# involves specifying the type parameter inside angle brackets (< >) after the class, method, or interface name.

1. Generic Class Syntax:

public class MyClass<T>
{
    private T data;

    public MyClass(T value)
    {
        data = value;
    }

    public T GetData()
    {
        return data;
    }
}

In this example:

  • T is the placeholder for the type that will be specified later.
  • You can create an instance of MyClass using any type: MyClass<int>, MyClass<string>, etc.

2. Generic Method Syntax:

public T Add<T>(T a, T b)
{
    return a + b;
}

In this example:

  • The method Add is generic, and the type T will be inferred based on the parameters passed when calling the method.

3. Generic Interface Syntax:

public interface IMyInterface<T>
{
    void DoSomething(T item);
}

Generics in C# with example

Let us look at an example to see why Generics are important in C#. We write a simple program to determine whether two integer numbers have the same value. The code for this is shown below. In this case, we named two classes CheckEqualOrNot and MainClass. The areBothEqual() method in the CheckEqualOrNot class takes two integer values as input parameters and checks whether the two input values are equal or not. If both the values are equal, it returns true; otherwise, it returns false. And we're calling the static areBothEqual() method from the MainClass class and displaying the output based on the return value.

  • C#

C#

using System;
namespace GenericsDemo
{
   public class MainClass
   {
        static void Main()
       {
           // creating an instance of CheckEqualOrNot class   
          CheckEqualOrNot obj= new CheckEqualOrNot();
         
          // isEqual stores the true or false value
          bool isEqual= obj.areBothEqual(10,10);
         
          if(isEqual==true){
              Console.WriteLine("Both the values are equal");
          }
          else{
              Console.WriteLine("Both the values are not equal");
          }
         
       }
   }


   public class CheckEqualOrNot
   {
       // simple function that returns true if both the values
       // are same, otherwise it returns false
       public bool areBothEqual(int value1, int value2)
       {
           if(value1 == value2){
               return true;
           }
           else{
               return false;
           }
       }
      
   }
}

Output: 

Both the values are equal

The issue with the above program is that we have to create multiple functions to compare values of different data types. For example, to check if two values of string data type are the same or not, we need one more function.

Example:

  • C#

C#

using System;
namespace GenericsDemo
{
   public class MainClass
   {
        static void Main()
       {
           // creating an instance of CheckEqualOrNot class   
          CheckEqualOrNot obj= new CheckEqualOrNot();
          // checks for integer values
          bool isEqual= obj.areBothEqual(10,10);
          if(isEqual==true){
              Console.WriteLine("Both the values are equal");
          }
          else{
              Console.WriteLine("Both the values are not equal");
          }
         
           // creating an instance of CheckEqualOrNot class
          CheckEqualOrNot obj1= new CheckEqualOrNot();
          //checks for two string values
          bool isEqual1= obj1.areBothEqual("Ram","Lakshman");
          if(isEqual1==true){
              Console.WriteLine("Both the values are equal");
          }
          else{
              Console.WriteLine("Both the values are not equal");
          }
       }
   }


   public class CheckEqualOrNot
   {
       // it receives two integer parameters
       public bool areBothEqual(int value1, int value2)
       {
           if(value1 == value2){
               return true;
           }
           else{
               return false;
           }
       }
       // receives two string parameters
       public bool areBothEqual(string value1, string value2)
       {
           if(value1 == value2){
               return true;
           }
           else{
               return false;
           }
       }
   }
}

Output:

Both the values are equal
Both the values are not equal

Solutions To The Above Problem

We saw that to compare variables of different data types, we had to create different functions which could take the values of the data type which we wanted to compare. If we try to pass integer values for a string parameter, there will be an error.

There are two ways to solve the problem:

  1. Make use of the object data type as the parameters.
  2. Use generics.

Object Data Type

One of the ways to make the above areBothEqual() method to accept string type values as well as integer type values is to make use of the object data type as the parameters. If we make the parameters of the areBothEqual() method as Object type, then it is going to work with any data type.

Example:

  • C#

C#

using System;
namespace GenericsDemo
{
   public class MainClass
   {
        static void Main()
       {
           // creating an instance of CheckEqualOrNot class   
          CheckEqualOrNot obj= new CheckEqualOrNot();
          // checks for integer objects
          bool isEqual= obj.areBothEqual(12,10);
          checkAndPrint(isEqual);
         
          CheckEqualOrNot obj1= new CheckEqualOrNot();
          // checking for string objects
          bool isEqual1= obj1.areBothEqual("Ram","Ram");
          checkAndPrint(isEqual1);
       }
      
       // function to check the value and print the result
       static void checkAndPrint(bool result){
           if( result == true){
               Console.WriteLine("Both the values are equal");
           }else{
               Console.WriteLine("Both the values are not equal");
           }
       }
   }


   public class CheckEqualOrNot
   {
       // the data types provided are converted to objects
       // we can provide any data type
       public bool areBothEqual(object value1, object value2)
       {
           if(value1 == value2){
               return true;
           }
           else{
               return false;
           }
       }
     
      
   }
}

Output: 

Both the values are not equal
Both the values are equal

Since the output of the above code is as expected, you may feel that this is the best solution for our problem. However, there are certain flaws in creating object data types.

  • The overall performance of the program is reduced and compromised due to boxing and unboxing. Boxing is a mechanism to explicitly convert a value type to a reference type, whereas unboxing is the process of converting back the reference type into the value type. Whenever we pass an integer or a string or a float value in the areBothEqual() method, it is converted to object type. We are actually wasting time in boxing and unboxing.
  • Since the parameters are object types, we can pass two values of different data types, and there will be no error. However, it makes no sense to compare two different data types. The program now no longer remains strongly typed.

Because of these two big flaws with the object data types, the concept of generics was brought up.

Generics

The solution to the above problems is Generics in C#. With generics, we will make the areBothEqual() method to work with different types of data types. The most common way of defining a Generic class is by putting the <T> sign after the class name. It is not mandatory to put the "T" word in the Generic type definition. We can also use any other word.

Generic Method

In order to make the areBothEqual() method generic (generic means the same method will work with the different data type), we specified the type parameter T using the angular brackets <T>.

Example:

  • C#

C#

using System;
namespace GenericsDemo
{
   public class MainClass
   {
        static void Main()
       {
           // creating an instance of CheckEqualOrNot class   
          CheckEqualOrNot obj= new CheckEqualOrNot();
         
          // whenever we call the areBothEqual<T>() method
          //we specify the data type for which the comparison is made.
          //if two different data type is provided there will be compile-time error
         
          // specify the parameter using <string>
          bool isEqual1= obj.areBothEqual<string>("Ram","Ram");
          checkAndPrint(isEqual1);
         
          // specify the parameter using <int>
          bool isEqual2= obj.areBothEqual<int>(10,10);
          checkAndPrint(isEqual2);
         
          // specify the parameter using <int>
          bool isEqual3= obj.areBothEqual<int>(10,12);
          checkAndPrint(isEqual3);
         
          // specify the parameter using <float>
          bool isEqual4= obj.areBothEqual<float>(2.34f,2.35f);
          checkAndPrint(isEqual4);
         
          // specify the parameter using <double>
          bool isEqual5= obj.areBothEqual<double>(6.8765,6.8765);
          checkAndPrint(isEqual5);
         
       }
      
       // function to check the value and print the result
       static void checkAndPrint(bool result){
           if( result == true){
               Console.WriteLine("Both the values are equal");
           }else{
               Console.WriteLine("Both the values are not equal");
           }
       }
   }


   public class CheckEqualOrNot
   {
      
       //the generic method
       // created by just using angular<T>
       //We do not specify a particular data type for the parameters
       // even they are generic
       // Ex: we don't write string value1, rather we write T value1
       // The data type for value1 is specified by the user while calling the function
       public bool areBothEqual<T>(T value1, T value2)
       {
           if(value1.Equals(value2)){
               return true;
           }
           else{
               return false;
           }
       }
     
   }
}

Output:

Both the values are equal
Both the values are equal
Both the values are not equal
Both the values are not equal
Both the values are equal

Explanation:

  • In order to make the areBothEqual() method generic, we specified the type parameter T using the angular brackets <T>.
  • In order to call the areBothEqual() method, we must specify the data type on which the method should operate while calling the function.
  • This is how a function is made generic.

Creating a Generic Class

Just as we made a method generic, we can make the entire class generic.

Example: 

  • C#

C#

using System;


namespace DemonstrateGenerics
{
   // creating a generic class
   // simply add <T> after the class name
   public class GenericClass<T>
   {
      
       public T genericVariable; // variable of generic type
      
       // generic method
       // simply add <T> after method name and specify the parameters as generic
       public void genericMethod<T>(T value1, T value2)
       {
           Console.WriteLine("{0}", genericVariable);
           Console.WriteLine("Value1:{0}", value1);
           Console.WriteLine("Value2: {0}", value2);
       }
   }
  
   // This class calls the instances of the generic class
   class NormalClass
   {
       static void Main(string[] args)
       {
          
           /*
           The below code shows how to Instantiate a Generic class.
           For every instance, its respective data type must be specified.
           If we try to provide a value that is not of the same type
           as specified, there will be a compile-time error.
           */
          
           // Instantiate Generic Class, string is the type argument
           GenericClass<string> stringObj = new GenericClass<string>();
           stringObj.genericVariable = "Want to become a coder?";
           stringObj.genericMethod("Coding", "Ninja");
          
           Console.WriteLine("********************");
          
           // Instantiate Generic Class, int is the type argument
           GenericClass<int> intObj = new GenericClass<int>();
           intObj.genericVariable = 100;
           intObj.genericMethod(200, 300);
          
           Console.WriteLine("********************");
          
           // Instantiate Generic Class, double is the type argument
           GenericClass<double> doubleObj = new GenericClass<double>();
           doubleObj.genericVariable = 50.7;
           doubleObj.genericMethod(109, 2345.789);
          
       }
   }
}

Output: 

Want to become a coder?
Value1:Coding
Value2: Ninja
********************
100
Value1:200
Value2: 300
********************
50.7
Value1:109
Value2: 2345.789

Features of Generics

The features of generics are:

Type Safety

One of the biggest features of generics is that they are type-safe. If you try to use a data type other than the one specified in the definition, you will get a compile-time error.

Code Reusability 

Generics increase the reusability of the code. By making the methods and variables generic, we don't have to write code again and again to handle different data types.

Enhanced Performance

If we use object data type, then there is boxing and unboxing involved in the process. However, using generics we can avoid boxing and unboxing and hence save crucial time which is used for boxing and unboxing. This increases overall performance.

Advantages of C# Generics

  • Type Safety: Generics ensure that type errors are caught at compile time, preventing runtime errors related to type mismatches. This eliminates the need for casting and minimizes the risk of runtime exceptions.
  • Code Reusability: With generics, you can create flexible classes, methods, and data structures that work with any data type, leading to reusable code across different scenarios without rewriting.
  • Performance: Generics help avoid boxing and unboxing operations, which are common when using non-generic collections with value types. This improves performance, particularly in scenarios involving large amounts of data.
  • Increased Maintainability: By using generics, the codebase becomes easier to maintain since you avoid duplication. The same generic class or method can handle different types, making it more concise and easier to update.
  • Cleaner Code: Generic types make the code more concise and readable by eliminating the need for explicit casting or writing multiple overloaded methods or classes for each type.
  • Better Compatibility with LINQ: C#'s LINQ (Language Integrated Query) works seamlessly with generics, allowing you to write efficient, type-safe queries over collections without the need for casts or conversions.
  • Supports Strongly Typed Collections: Generic collections such as List<T>, Dictionary<TKey, TValue>, and Queue<T> provide strongly-typed data structures that ensure the correct type is always used, reducing runtime errors.
  • Flexibility in Data Handling: Generics allow you to create algorithms and data structures that are independent of the data type, enabling you to handle any type of data without losing type safety.

Frequently Asked Questions

When should I use generics in C#?

Use generics in C# when you need reusable, type-safe code that works with multiple data types without sacrificing performance or flexibility.

What is generics in C# Unity?

In Unity, generics in C# allow for creating reusable scripts and data structures that can work with any type of data, improving code flexibility and maintainability.

Why are generics used?

It is possible to create a single class, interface, or method that automatically works with different types of data by using generics.

How can we make a method generic?

To make a method generic, we specify the type parameter T using the angular brackets <T>.

How do generics improve the performance of the program?

Generics we can avoid boxing and unboxing and hence save crucial time. This increases overall performance.

Conclusion

In this article, we have extensively discussed Generics in C# along with its code implementation in C#.

  • Generics simply refers to parameterized types. Parameterized types are useful because they allow us to create classes, interfaces, and methods that take as a parameter the type of data on which they operate.
  • Generics provided the type safety that was previously lacking.
  • The most common way of defining a Generic class is by putting the <T> sign after the class name. It is not mandatory to put the "T" word in the Generic type definition.
Live masterclass