Code360 powered by Coding Ninjas X Naukri.com. Code360 powered by Coding Ninjas X Naukri.com
Last Updated: Mar 27, 2024
Difficulty: Easy

C# Generics

Leveraging ChatGPT - GenAI as a Microsoft Data Expert
Speaker
Prerita Agarwal
Data Specialist @
23 Jul, 2024 @ 01:30 PM

Introduction

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.

It is possible to create a single class that automatically works with different types of data by using generics. A generic class, interface, or method is one that operates on a parameterized type, as in a generic class or generic method.

Generics provided the type safety that was previously lacking. They also streamlined the process by eliminating the need to explicitly use casts to translate between Object and the type of data being operated on. All casts are automatic and implicit with generics. As a result, generics broadened our horizons.

Before we move on to understand generics with the help of code, it is important to understand why we even need generics. Once we understand that the concept of generics will be crystal clear and the definitions that we learned above will start making much more sense.

Need for Generics

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.

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:

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.
Get the tech career you deserve, faster!
Connect with our expert counsellors to understand how to hack your way to success
User rating 4.7/5
1:1 doubt support
95% placement record
Akash Pal
Senior Software Engineer
326% Hike After Job Bootcamp
Himanshu Gusain
Programmer Analyst
32 LPA After Job Bootcamp
After Job
Bootcamp

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:

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:

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: 

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.

FAQs

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.

Which type of error is reflected if we try to use two different data types in a generic method?
Compile-time error.

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.

We hope that this blog has helped you enhance your knowledge regarding Generics and if you would like to learn more, check out our articles here

Do upvote our blog to help other ninjas grow. Happy Coding!

Topics covered
1.
Introduction
2.
Need for Generics
2.1.
Solutions To The Above Problem
3.
Object Data Type
4.
Generics
4.1.
Generic Method
4.2.
Creating a Generic Class
5.
Features of Generics
5.1.
Type Safety
5.2.
Code Reusability 
5.3.
Enhanced Performance
6.
FAQs
6.1.
Why are generics used?
6.2.
How can we make a method generic?
6.3.
How do generics improve the performance of the program?
6.4.
Which type of error is reflected if we try to use two different data types in a generic method?Compile-time error.
7.
Conclusion