C# Generics


In this blog I will very briefly discuss some of the concepts of generics. I also want to mention that generics is a large topic so please see MSDN for reference. I will try to deliver some general concepts about generics.

What are Generics?
Generics allow us to write classes, structures, methods, interfaces or delegate in such a way that they operate on the type of data specified in the parameter <T>. So for example, we write a general code for a class and it can perform that functionality on different types of data mentioned in the type parameter.

Can’t we do the same with object class?
Well not in all cases. Generics allow us to write a general code which will compile as if it were written on the type we specified in parameter. Also, if we try to get this functionality with object class, there is a lot of burden in doing so, such as downcasting every time we need to operate on specific type, boxing & unboxing can be an overhead on processing.

Why we need Generics?

  1. Generics provide type safety, so no explicit downcasting is required and there is no chance of type mismatch at runtime causing a runtime exception (as we know, for example that downcasting an object into an int will be done at runtime) because we will be able to know the type at compile time.
  2.  There is no overhead of boxing and unboxing when dealing with primitive types that we would have, if we tried to get this functionality with object class.
  3. Often, we are overloading a method or creating several version of a class that implement same algorithm but on different data types e.g., in case of data structures. In such condition we can have one generic code that implements the logic. Remember that generic methods can also be overloaded just like other method as long as their method signature is different.
Generic Class
Syntax
To create a generic class, we need to specify generic parameter type list that it uses within <> after the class name, you can give any name to the type parameter but generally <T> or <T, V> is used. Then you can write code for that class and implement that logic with T or V considering them as if they are type like an int or double.

Example 1: Creating a simple Generic class
namespace HelloWorld
{
    class GenericClass<T>
    {
        T typeVariable;
        public GenericClass(T t)
        {
            this.typeVariable = t;
        }
        public void DisplayType()
        {
            Console.WriteLine("Type of T is " + typeof(T) );
        }
        public T GetMyVariable()
        {
            return typeVariable;
        }
    }
    class Demo
    {
        static void Main(string[] args)
        {
            GenericClass<int> myIntObj = new GenericClass<int>(2);
            GenericClass<double> myDoubleObj = new GenericClass<double>(2.2);
            GenericClass<string> myStringObj = new GenericClass<string>("Hello World!");
            GenericClass<Person> myPersonObj = new GenericClass<Person>(new Person("Imran Aftab"));

            int a = myIntObj.GetMyVariable();
            double b = myDoubleObj.GetMyVariable();
            string c = myStringObj.GetMyVariable();
            Person d = myPersonObj.GetMyVariable();

            myIntObj.DisplayType();
            myDoubleObj.DisplayType();
            myStringObj.DisplayType();
            myPersonObj.DisplayType();

            Console.ReadKey();
        }
        class Person
        {
            public string Name { get; set; }
            public Person(string name)
            {
                this.Name = name;
            }
        }
    }
}
OUTPUT:
Type of T is System.Int32
Type of T is System.Double
Type of T is System.String
Type of T is HelloWorld.Demo+Person

The example above shows the power of using generics and; how generics gives flexibility and reusability to our code.

First, we created a generic class indicated by <> where the class name is GenericClass and type parameter is T. T is now a generic type which will be replaced at compile type with the type mentioned in the type parameter when creating an instance of it.   Next, in this class we have a constructor and two instance methods name DisplayType and GetMyVariable. DisplayType returns the type specified within angle brackets and GetMyVariable returns the variable of type T.
In the Demo class, we have an inner class Person which is accessible only through Demo class only. In Person class, we only have one property named Name and a constructor which gives initializes Name property.
In the main method, we can see the magic of using a generic class that we don’t need to downcast our typeVariable when type T is returned from GetMyVariable method. Also, we can see from the results that the type of typeVariable changes to whatever type is mentioned within <> while creating an instance of it and DisplayType method shows the type mention in parameters of class instead of type T.

Generic Method
So far, we have created a generic class which uses a parameterized type to create T’s type field named typeVariable in the GenericClass class. Let’s see how we can create a method in a similar way.
Syntax
Creating a generic method is similar to creating a generic class. Just put generic type <T> after the method name and use that type in your method.
Modifier Return-type Method-Name<T>(Parameter-List)
{
            //Statements;
}

Example 2: Creating a simple Generic method in non-generic class
namespace HelloWorld
{
    class GenericMethod
    {
        public static void MyMethod<T>(T[] col)
        {
            for (int i = 1; i <= col.Length; i++)
            {
                Console.WriteLine("item " + i + " is        " + col[i-1]);
            }
            Console.WriteLine();
        }
    }
    public class Demo
    {
        static void Main(string[] args)
        {
            int[] a = { 1,2,3,4,5};
            double[] b = { 6.5,7.4,8.3,9.2,10.1};
            string[] c = { "Hello", "World!"};
            Person[] d = {
                            new Person("Imran"),
                            new Person("Usman")
                         };
            GenericMethod.MyMethod<int>(a);
            GenericMethod.MyMethod<double>(b);
            GenericMethod.MyMethod<string>(c);
            GenericMethod.MyMethod<Person>(d);

            Console.ReadKey();
        }

        private class Person
        {
            public string Name { get; set; }
            public Person(string name)
            {
                this.Name = name;
            }
            public override string ToString()
            {
                return Name;
            }
           
        }
    }
   
}

OUTPUT
item 1 is        1
item 2 is        2
item 3 is        3
item 4 is        4
item 5 is        5

item 1 is        6.5
item 2 is        7.4
item 3 is        8.3
item 4 is        9.2
item 5 is        10.1

item 1 is        Hello
item 2 is        World!

item 1 is        Imran
item 2 is        Usman


Generic Constrained Type
Generic type parameter allows us the ability to operate on any type but often times we want to operate on a type which has some relation with the generic type so that we can operate on it. For example, if we want to operate on classes which are stream types then we need to ensure that the type parameter is a derived class of stream.
In these situation, we put a constraint on the parameter type of generics. For example, if we want to ensure the type is a collection, we would specify that it implements IEnumerable and so forth.
Generally, we can put following constraints on a parametric type of generic.
  • 1.     Base class constraint
  • 2.     Interface constraint
  • 3.     Constructor constraint
  • 4.     Reference type constraint
  • 5.     Value type constraint

Above mentioned constraints are self-explanatory but the constructor constraint might be a bit tricky because it only allows parameter-less constructor to be the constraint on the type parameter even if other constructors are available. So, either we should use the default constructor or you must define a parameter-less constructor.

Base class constraint 
As the name implies, the base class constraint requires a type parameter to be either the base class itself or a class which is derived from that class. This allow the type parameter to use properties or methods available in base class. Let’s see an example:

Example 3: Creating a Generic class with base class constraint
namespace HelloWorld
{
    public class Person
    {
        string name;
        string cnic;
        public Person(string name,string cnic)
        {
            this.name = name;
            this.cnic = cnic;
        }
        public virtual string getPersonData()
        {
            return "Name: " + this.name + "\n" + "CNIC: " + this.cnic;
        }
    }
    public class Student : Person
    {
        string studentId;
        public Student(string name, string cnic,string studentId) : base(name, cnic)
        {
            this.studentId = studentId;
        }
        public override string getPersonData()
        {
            return base.getPersonData() + "\n" + "Student Id: " + this.studentId;
        }
    }
    public class GenericConstraint<T> where T : Person
    {
        T ob;
        public GenericConstraint(T obj)
        {
            this.ob = obj;
           
        }
        public string getPersonData()
        {
            return ob.getPersonData();
        }
    }

    class Demo
    {
        static void Main(string[] args)
        {
              Student imran = new Student("Imran Aftab", "12345", "CIIT/ABC/LHR");
GenericConstraint<Student> s = new GenericConstraint<Student>(imran);//Student is a Person (Polymorphism)
            string data = s.getPersonData();//applies method overriding (Polymorphism)
            Console.WriteLine(data);

            Console.ReadKey();
        }
    }
}

In the above example, we created four classes. First class is the Person class which has two fields (name & cnic), a constructor with two parameters to initialize fields and a virtual method getPersonData that displays values of fields (which can be overridden by a class derived from Person).
The second class is Student which inherits Person class adds one more field studentId, a constructor to initialize fields of Student and overrides the getPersonData method to return those field as well as the new field studentId of this class.
The third class is the generic class GenericConstraint which shows how base class constraint work. To put a base class constraint, we need to put a where keyword after that we specify which type parameter must have which class. In this case, type parameter T must inherit Person or either itself should be a Person. This gives us the ability to access type parameter T’s member which in this case are the public members of Person class. In this generic class (GenericConstraint), we created an instance of T type (which is Person), a constructor to create object of T type and a method getPersonData which calls T’s instance method getPersonData and returns a string.
Finally, the fourth class is a Demo class, where we create an instance of Student (Polymorphism). After that we create an instance of GenericConstraint class and passes it the instance of Student. On GenericConstraint instance we call getPersonData method which calls derived class’s (Student) getPersonData method instead of base class (Person) method (Polymorphism).

Interface constraint 
Interface constraint is almost identical to base class constraint, the only thing that is different is that instead of base class the type parameter must implement that interface. Let’s quickly look at an example which is self-explanatory.
Example 4: Creating a Generic class with interface constraint
namespace HelloWorld
{
    public interface IMakeSound
    {
        string Sound { get; set; }
        void makeSound();
    }
    public class Animal : IMakeSound
    {
        //Implementing Interface Properties
        public string Name { get; set; }
        public string Sound{ get; set; }

        //Constructor
        public Animal(string name,string sound)
        {
            this.Name = name;
            this.Sound = sound;
        }
       
        //Interface Method
        public void makeSound()
        {
            Console.WriteLine(Name + " makes a sound like \"" + Sound + "\"");
        }
    }
    public class InterfaceConstraint<T> where T:IMakeSound
    {
        T ob;

        public InterfaceConstraint(T o)
        {
            ob = o;
        }

       
        public void Say()
        {
            ob.makeSound();
        }
    }

    class Demo
    {
        static void Main(string[] args)
        {
            Animal dog = new Animal("Dog", "Woof Woof!");
            InterfaceConstraint<IMakeSound> soundMakerA = new InterfaceConstraint<IMakeSound>(dog );
            soundMakerA.Say();

            Console.WriteLine();

            Animal cat = new Animal("Cat", "Meow Meow!");
            InterfaceConstraint<IMakeSound> soundMakerB = new InterfaceConstraint<IMakeSound>(cat);
            soundMakerB.Say();

            Console.ReadKey();

        }


    }
}
OUTPUT
Dog makes a sound like "Woof Woof!"

Cat makes a sound like "Meow Meow!"
I think this example is self-explanatory since nothing has changed except instead of base class we have an interface and the generic class type parameter is implementing that interface.

Constructor Constraint
As I said earlier that constructor constraint has the ability to put the constraint on type parameter to must have a parameter-less constructor or default constructor and you cannot constraint it to have particular constructor even if other constructors are available. This is done by using new() keyword after the where keyword. The rest is quite similar to the previous ones.

namespace HelloWorld
{
    class Product
    {
        string name;
        static long id;
        public Product()
        {
            id = 10000001;
            name = "N/A";
        }
        public override string ToString()
        {
            return "Product Name: " + name + "\n"+"id: " +id;
        }
    }
    class ConstructorConstraint<T> where T : new()
    {
        T ob;
        public ConstructorConstraint()
        {
            ob = new T();
        }
        public T getType()
        {
            return ob;
        }
    }
    class Demo
    {
        static void Main(string[] args)
        {
            ConstructorConstraint<Product> mangos = new ConstructorConstraint<Product>();
            Console.WriteLine(mangos.getType());
            Console.ReadKey();
        }
    }
}
OUTPUT
Product Name: N/A
id: 10000001

Reference Type Constraint
Reference type as its name implies restrict the type parameter to be a reference type only. This is done by using class keyword when defining constraint after where keyword.

namespace HelloWorld
{

    class ReferenceConstraint<T> where T : class
    {
        T ob;
        public ReferenceConstraint()
        {
            ob = null;
        }
    }
    public class Demo
    {
        ReferenceConstraint<Object> ob = new ReferenceConstraint<Object>();
        //ReferenceConstraint<int> ob1 = new ReferenceConstraint<int>(); compile time error
    }
   
}

Value Type Constraint
Value type as its name implies restrict the type parameter to be a value type only. This is done by using struct keyword when defining constraint after where keyword (as we know struct is a value type).

namespace HelloWorld
{
    class ValueTypeConstraint<T> where T : struct
    {
        T ob;
        public ValueTypeConstraint(T ob)
        {
            //ob = null; error
            this.ob = ob;
        }
    }
    class Test
    {
        ValueTypeConstraint<int> o = new ValueTypeConstraint<int>(10);
        //ValueTypeConstraint<Object> o = new ValueTypeConstraint<Object>(new Object()); compilation error

    }

}


Conclusion

Generics provide versatility to how C# language solve a problem by allow one code with a parametrized type to act like if it were written for that type at compile time. Generics may seem a bit sophisticated at first but becomes easy once you understand logic behind it. 

Comments

Popular posts from this blog

Implementing Basic and JWT Token authentication with C# .NET

.NET Core 3 officially comes to Windows IoT Core

Setting up Free Custom Domain on Microsoft Azure Web App Service

Setting up CI and CD pipeline in Azure DevOps for ASP.NET Core and Azure Web Apps

Microsoft Azure DevOps : A Complete CI & CD solution in the cloud

Securing Powershell Scripts with Code-Signing Certificate

Understanding Powershell ExecutionPolicy and securing Powershell CmdLets/Scripts with Code-Signing Certificate

Microsoft Azure Blob Storage - Managing Blobs and storage containers from C#

Xamrin Android Push Notification using Firebase Cloud Messaging

Fundamental of Powershell Scripting