Generics in C#

Generics in C#

In this article, I am going to discuss how to implement Generics in C# with examples. But before implementing the Generics in C#, let us first discuss why we need generics in C#.

Let us understand the need for Generics in C# with one example.

Let’s us create a simple program to check whether two integer numbers are equal or not.

namespace GenericsDemo
{
    public class ClsMain
    {
        private static void Main()
        {
            bool IsEqual = ClsCalculator.AreEqual(10, 20);
            if (IsEqual)
            {
                Console.WriteLine("Both are Equal");
            }
            else
            {
                Console.WriteLine("Both are Not Equal");
            }

            Console.ReadKey();
        }
    }

    public class ClsCalculator
    {
        public static bool AreEqual(int value1, int value2)
        {
            return value1 == value2;
        }
    }
}

The above code implementation is very straight forward. Here we created two classes with the name ClsCalculator and ClsMain. Within the ClsCalculator class, we have AreEqual() method which takes two integer values as the input parameter and then it checks whether the two input values are equal or not. If both are equal then it returns true else it will return false.

The above AreEqual() method works as expected as it is and more importantly it will only work with the integer values as this is our initial requirement. Suppose our requirement changes, now we also need to check whether two string values are equal or not.

In the above example, if we try to pass values other than the integer values, then we will get a compile-time error. This is because the AreEqual() method of the ClsCalculator class is tightly bounded with the integer data type and hence it is not possible to invoke the AreEqual method other than the integer data type values. So, when we try to invoke the AreEqual() method by passing string values as shown below we get a compile-time error.

bool Equal = ClsCalculator.AreEqual(“ABC”, “XYZ”);

One of the way to make the above AreEqual() method to accepts string type values as well as integer type values, we need to make use of the object data type as the parameters. If we make the parameters of the AreEqual() method as Object type, then it is going to works with any data type.

Note: The most important point that you need to keep in remember is every .NET data type whether it is a primitive type of reference type, is directly or indirectly inherits from the System.Object data type.

Let’s modify the AreEqual() method to use the Object data type as shown below.
namespace GenericsDemo
{
    public class ClsMain
    {
        private static void Main()
        {
           // bool IsEqual = ClsCalculator.AreEqual(10, 20);
            bool IsEqual = ClsCalculator.AreEqual("ABC", "ABC");
            if (IsEqual)
            {
                Console.WriteLine("Both are Equal");
            }
            else
            {
                Console.WriteLine("Both are Not Equal");
            }
            Console.ReadKey();
        }
    }

    public class ClsCalculator
    {
        //Now this method can accept any data type
        public static bool AreEqual(object value1, object value2)
        {
            return value1 == value2;
        }
    }
}

That’s it. Run the application and you will see it is working as expected.

Let’s see the problem of the above code implementation.

  1. We get poor Performance due to boxing and unboxing. The object type needs to be converted to the value type.
  2. Now, the AreEuqal() method is not type-safe. Now it is possible to pass a string value for the first parameter and an integer value for the second parameter. 
Another option is we need to overload the AreEqual method which will accept different types of parameters as shown below.
namespace GenericsDemo
{
    public class ClsMain
    {
        private static void Main()
        {
           // bool IsEqual = ClsCalculator.AreEqual(10, 20);
           // bool IsEqual = ClsCalculator.AreEqual("ABC", "ABC");
            bool IsEqual = ClsCalculator.AreEqual(10.5, 20.5);

            if (IsEqual)
            {
                Console.WriteLine("Both are Equal");
            }
            else
            {
                Console.WriteLine("Both are Not Equal");
            }

            Console.ReadKey();
        }
    }

    public class ClsCalculator
    {
        public static bool AreEqual(int value1, int value2)
        {
            return value1 == value2;
        }

        public static bool AreEqual(string value1, string value2)
        {
            return value1 == value2;
        }

        public static bool AreEqual(double value1, double value2)
        {
            return value1 == value2;
        }
    }
}

Here we have created three methods with the same name but with the different type of parameters. This is nothing but method overloading. Now, run the application and you will see everything is working as expected.

The problem with the above code implementation is that we are repeating the same logic in each and every method. However, if tomorrow we need to compare two float or two long values then again we need to create two more methods.

We can solve all the above problems with generics in C#. With generics, we make the AreEqual() method to works with different types of data types. Let us first modify the code implementation to use the generics and then we will discuss how it works.

Let’s modify the AreaEqual() method as shown below to use Generics in C#.
namespace GenericsDemo
{
    public class ClsMain
    {
        private static void Main()
        {
            //bool IsEqual = ClsCalculator.AreEqual<int>(10, 20);
            //bool IsEqual = ClsCalculator.AreEqual<string>("ABC", "ABC");
            bool IsEqual = ClsCalculator.AreEqual<double>(10.5, 20.5);
            if (IsEqual)
            {
                Console.WriteLine("Both are Equal");
            }
            else
            {
                Console.WriteLine("Both are Not Equal");
            }
            Console.ReadKey();
        }
    }

    public class ClsCalculator
    {
        public static bool AreEqual<T>(T value1, T value2)
        {
            return value1.Equals(value2);
        }
    }
}

Here in the above example, in order to make the AreEqual() 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>. Then we use that type as the data type for the method parameters as shown in the below image.

Generics in C#

At this point, if you want to invoke the above AreEqual() method, then you need to specify the data type on which the method should operate. For example, if you want to work with integer values, then you need to invoke the AreEqual() method by specifying int as the data type as shown in the below image using angular brackets.

Invoking Generic Methods in C#

The above AreEqual() generic method is work as follows:

How the Generic Methods work in C#

If you want to work with the string values, then you need to call the AreEqual() method as shown below.

bool IsEqual= ClsCalculator.AreEqual<string>(“ABC”, “ABC”);

Now, I hope you understand the need and importance of Generics in C#.

What is Generics in C#?

As we already discussed in our previous article, the Generics in C# are introduced as part of C# 2.0. The Generics in C# allows us to define classes and methods which are decoupled from the data type. In other words, we can say that the Generics allows us to create classes using angular brackets for the data type of its members. At compilation time, these angular brackets are going to be replaced with some specific data type.

In C#, the Generics can be applied to the following:
  1. Interface
  2. Abstract class
  3. Class
  4. Method
  5. Static method
  6. Property
  7. Event
  8. Delegates
  9. Operator
Advantages of Generics in C#
  1. It Increases the reusability of the code.
  2. The Generics are type safe. We will get the compile-time error if we try to use a different type of data rather then the one we specified in the definition.
  3. We get better performance with Generics as it removes the possibilities of boxing and unboxing.
Let’s us see how to use Generics with class and its members.

Let us create a generic class with a generic constructor, generic member variable, generic property, and a generic method as shown below.

using System;

namespace GenericsDemo
{
    //MyGenericClass is a Generic Class
    class MyGenericClass<T>
    {
        //Generic variable
        //The data type is generic
        private T genericMemberVariable;

        //Generic Constructor
        //Constructor accepts one parameter of Generic type
        public MyGenericClass(T value)
        {
            genericMemberVariable = value;
        }

        //Generic Method
        //Method accepts one Generic type Parameter
        //Method return type also Generic
        public T genericMethod(T genericParameter)
        {
            Console.WriteLine("Parameter type: {0}, value: {1}", typeof(T).ToString(), genericParameter);
            Console.WriteLine("Return type: {0}, value: {1}", typeof(T).ToString(), genericMemberVariable);
            return genericMemberVariable;
        }

        //Generic Property
        //The data type is generic
        public T genericProperty { get; set; }
    }
}

In the above example, we created the class MyGenericClass with <T>. The angular brackets (“<>”) indicates that the MyGenericClass class is a generic class and the type for this class is going to be defined later.

While creating the instance of this MyGenericClass class, we need to specify the type and the compiler will assign that type to T. In the following example, we use int as the data type:

class Program
{
    static void Main()
    {
        MyGenericClass<int> integerGenericClass = new MyGenericClass<int>(10);

        int val = integerGenericClass.genericMethod(200);
        Console.ReadKey();
    }
}

Run the application and it will give you the following output.

Generics in C#

The following diagram shows how the T will be replaced with the int data type by the compiler.

Generics in C#

The compiler will compile the above class as shown in the below image

Generics in C#

At the time of instantiation, we can use any type as per our requirement. If we want to use a string type, then we need to instantiate the class as shown below

class Program
{
    static void Main()
    {
        MyGenericClass<string> stringGenericClass = new MyGenericClass<string>("Hello Generic World");
        stringGenericClass.genericProperty = "This is a generic property example.";
        string result = stringGenericClass.genericMethod("Generic Parameter");
        Console.ReadKey();
    }
}
OUTPUT:

Generics in C#

I hope you understand the generics in C#. The generics are extremely used by the collection classes which belong to System.Collections.Generic namespace. In the next article, I am going to discuss the List Generic Collection class in C# with examples.

SUMMARY:

In this article, I try to explain Generics in C# with an example. I hope this article will help you with your need. I would like to have your feedback. Please post your feedback, question, or comments about this article.

Leave a Reply

Your email address will not be published. Required fields are marked *