Wednesday, May 26, 2021

C# Generics

Generics are classes, structures, interfaces, methods, delegates and events that have type parameters for one or more of the types that they store or use. Generics are used mainly for creating type safe collections. Generics improves code usability, type safety and performance. System.Collections.Generic namespace contains a number of built-in generic collections.

Important Terms


Type Parameter: Generic type parameters, or type parameters, are the placeholders in a generic type definition or method definitionGenerics use type parameter to represent a placeholder for type; the type of type parameter is determined when generic type is initialized. We can use any letter or word to denote type parameter such as T, TResult etc.

Generic Type: The general term generic type includes both constructed types and generic type definitions.

Generic Type Definition: A generic type definition is a class, structure, or interface declaration that functions as a template, with placeholders for the types that it can contain or use. Because a generic type definition is only a template, you cannot create instances of a class, structure, or interface that is a generic type definition.  The generic type definition must have type parameter(s) written inside angular bracket e.g. <T> after the name of generic class, generic interface or generic method.

Constructed Generic Type: A constructed generic type, or constructed type, is the result of specifying types for the generic type parameters of a generic type definition. For example, List<string> is a constructed generic type but List<T> is not.

Generic Type Argument: A generic type argument is any type that is substituted for a generic type parameter.
Generic type can be generic class, generic interface, generic method, generic delegate and generic event.

Constraints inform the compiler about the capabilities a type argument must have. Without any constraints, the type argument could be any type.

Generic Method Definition: A generic method definition is a method with two parameter lists: a list of generic type parameters and a list of formal parameters. Type parameters can appear as the return type or as the types of the formal parameters, as the following code shows.

T MyGenericMethod(T arg)
{
    T temp = arg;
    //...
    return temp;
}

A generic method might use its type parameter as the type of its return value or as the type of one of its formal parameters. Generic methods can appear on generic or nongeneric types.

class A
{
    T G(T arg)
    {
        T temp = arg;
        //...
        return temp;
    }
}
class MyGenericClass
{
    T M(T arg)
    {
        T temp = arg;
        //...
        return temp;
    }
}

Example to explain why generics are needed


using System;
namespace ConsoleAppGen
{
    class Adder
    {
        int x, y;
        public int Sum(int number1, int number2)
        {
            this.x = number1;
            this.y = number2;
            return this.x + this.y;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Adder adder = new Adder();
            Console.WriteLine("SUM:{0} ", adder.Sum(2,5));
            Console.ReadKey();
        }
    }
}
The above example adds two integer numbers. To add two floating point numbers, we will have to create another method in the same class or in another class as shown below.

using System;
namespace ConsoleAppGen
{
    class Adder
    {
        float x, y;
        public float Sum(float number1, float number2)
        {
            this.x = number1;
            this.y = number2;
            return this.x + this.y;
        }
        int a, b;
        public int Sum(int number1, int number2)
        {
            this.a = number1;
            this.b = number2;
            return this.a + this.b;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Adder adder = new Adder();
            Console.WriteLine("SUM:{0} ", adder.Sum(2, 5));
            Console.WriteLine("SUM:{0} ", adder.Sum(2.2f, 5.6f));
            Console.ReadKey();
        }
    }
}
To avoid creating multiple methods for same operation on different datatypes, we use type parameter in the following example. We have replaced the datatype by T symbol, T is called type parameter

using System;
namespace ConsoleAppGen
{
    class Adder<T>
    {
        T x, y;
        public T Sum(T number1, T number2)
        {
            this.x = number1;
            this.y = number2;
            return this.x + this.y; // compiler throws exception here
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Adder<float> adder = new Adder<float>();
            Console.WriteLine("SUM:{0} ", adder.Sum(2.2f,5.6f));
            Console.ReadKey();
        }
    }
}
The above code throws exception because the type of x, y, number1 and number2 is unknown to compiler and as it cannot perform computation upon unknown types. Now, we modify the above code to remove the exception. The modified code is as follows.

using System;
namespace ConsoleAppGen
{
    class Adder<t>
    {
        T x, y;
        public T Sum(T number1, T number2)
        {
            dynamic x = number1;// compiler ignores this line
            dynamic y = number2;// compiler ignores this line
            return x + y;// compiler ignores this line
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Adder<float> adder = new Adder<float>();
            Console.WriteLine("SUM:{0} ", adder.Sum(2.2f,5.6f));
            Console.ReadKey();
        }
    }
}
dynamic KEYWORD
Now, we consider about dynamic keyword. If during the compilation, the datatype is not determined, we can use dynamic datatype for a variable or type. At run time, the dynamic keyword will decide the datatype of the variable or type. We again modify the Main method of above code in the below code. Now, we can add multiple data types. This is the advantage of using generics in C#. It improves the code efficiency and type safety. Still we can pass string type data like Adder<string> which will throw exception. We can use constraint in generics to avoid this situation. We will see it later.

static void Main(string[] args)
        {
            Adder<float> adder = new Adder<float>();
            Console.WriteLine("SUM:{0} ", adder.Sum(2.2f,5.6f));
            Adder<int> adder = new Adder<int>();
            Console.WriteLine("SUM:{0} ", adder.Sum(2,5));
            Adder<double> adder = new Adder<double>();
            Console.WriteLine("SUM:{0} ", adder.Sum(2.2,5.6));
            Console.ReadKey();
        }

Thus, using generics, it is possible to design classes and methods that defer the specification of one or more types until the class or method is declared and instantiated by client code.

Advantages of Generics
Generic classes and methods combine reusability, type safety, and efficiency in a way that their non-generic counterparts cannot. 
Reusability:- In the above examples, we see that same generic class can be used to add multiple datatypes.
Type Safety:- The generic class is instantiated with a certain datatype. For example, Adder<float> adder = new Adder<float>(); we create adder object which can add only floating point numbers. If double datatype numbers will be used to add two numbers with this object, the compiler will throw exception.
Efficiency: As we don't have to write different methods or classes for different datatypes, the code is reduced which in turn increases the efficiency of the code.

Generics are most frequently used with collections and the methods that operate on them. More we can read at https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/types/generics

Another Example

using System;
namespace ConsoleApp3
{
    
    class Program
    {
        static void Swap(int a, int b)
        {
            Console.WriteLine("Before swap:{0} {1}" , a,b);
            int temp;
            temp = a;
            a = b;
            b = temp;
            Console.WriteLine("After swap:{0} {1}", a, b);
        }
        static void Main(string[] args)
        {
            Swap(2, 3);
            Console.ReadKey();
        }
    }
}
Converted into Generic Method

using System;
namespace ConsoleApp3
{    
    class Program
    {
        static void Swap<t>(T a, T b)
        {
            
            T temp;
            temp = a;
            a = b;
            b = temp;
            
        }
        static void Main(string[] args)
        {
            int a=2, b = 3;
            Console.WriteLine("Before swap:{0} {1}", a, b);
            Swap(a, b);
            Console.WriteLine("After swap:{0} {1}", a, b);
            Console.ReadKey();
        }
    }
}

C# Generic List <T> Example

using System;

using System.Collections.Generic;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            List&lt;string&gt; students = new List&lt;string&gt;();
            //Adding some items to sorted list
            students.Add("Jack");
            students.Add("Jill");
            students.Add("Peter");
            students.Add("Robins");
            students.Add("Clark");
            //Traverse using foreach
            foreach (var student in students)
            {
                Console.WriteLine(student);
            }
        }
    }
}

OUTPUT

  • Jack
  • Jill
  • Peter
  • Robins
  • Clark

C# Generic SortedList <TKey,Tvalue> Example

The keys of SortedList are sorted internally. Look at the output of the example.


using System;
using System.Collections.Generic;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            SortedList<int, string> students = new SortedList<int, string>();
            //Adding some items to sorted list
            students.Add(1, "Jack");
            students.Add(7, "Jill");
            students.Add(3, "Peter");
            students.Add(4, "Robins");
            students.Add(5, "Clark");

            //Traverse using for loop
            for (int i = 0; i < students.Count; i++)
            {
                Console.WriteLine("Key : " + students.Keys[i] + " - Value : " + students.Values[i]);
            }
            Console.WriteLine();
            //Traverse using foreach
            foreach (KeyValuePair<int, string> k in students)
            {
                Console.WriteLine("Key : {0} - Value : {1}", k.Key, k.Value);
            }
        }
    }
}

OUTPUT:

  • Key : 1 - Value : Jack
  • Key : 3 - Value : Peter
  • Key : 4 - Value : Robins
  • Key : 5 - Value : Clark
  • Key : 7 - Value : Jill

C# Generic SortedDictionary vs Generic SortedList

  • The keys of SortedDictionary are sorted internally. 
  • The SortedDictionary cannot be indexed like SortedList. Unlike SortedList, we cannot use for loop with SortedDictionary.
  • SortedList uses less memory than SortedDictionary but SortedDictionary has faster insertion and removal operations for unsorted data than SortedList.
  • If the list is populated all at once from sorted data then SortedList works faster than SortedDictionary.

C# Generic SortedDictionary<TKey,Tvalue> Example


using System;
using System.Collections.Generic;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            SortedDictionary<int, string> students = new SortedDictionary<int, string>();
            //Adding some items to sorted list
            students.Add(1, "Jack");
            students.Add(7, "Jill");
            students.Add(3, "Peter");
            students.Add(4, "Robins");
            students.Add(5, "Clark");

            Console.WriteLine();
            //Traverse using foreach
            foreach (KeyValuePair<int, string> k in students)
            {
                Console.WriteLine("Key : {0} - Value : {1}", k.Key, k.Value);
            }
        }
    }
}

  • Key : 1 - Value : Jack
  • Key : 3 - Value : Peter
  • Key : 4 - Value : Robins
  • Key : 5 - Value : Clark
  • Key : 7 - Value : Jill


No comments:

Post a Comment

Hot Topics