C# Generics Tutorial

1- Generic Class, Interface

1.1- Class Generics

The example below defines a  generics class.  KeyValue is a generics class that contains pairs of key and value.
KeyValue.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   public class KeyValue<K, V>
   {

       private K key;
       private V value;

       public KeyValue(K key, V value)
       {
           this.key = key;
           this.value = value;
       }

       public K GetKey()
       {
           return key;
       }

       public void SetKey(K key)
       {
           this.key = key;
       }

       public V GetValue()
       {
           return value;
       }

       public void SetValue(V value)
       {
           this.value = value;
       }

   }

}
K, V in   KeyValue <K, V> class  is called generics parameters which  is a certain type of data. When using this class, you must determine the specific parameter.

Take the example of using   KeyValue class
KeyValueDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   public class KeyValueDemo
   {

       public static void Main(string[] args)
       {

           // Create KeyValue object.
           // int: Phone Number (K = int)
           // string: Name (V = string)
           KeyValue<int, string> entry = new KeyValue<int, string>(12000111, "Tom");

           // C# understands that the return type is a int
           int phone = entry.GetKey();

           // C# understands that the return type is a string
           string name = entry.GetValue();

           Console.WriteLine("Phone = " + phone + " / name = " + name);

           Console.Read();
       }

   }

}
Running Example:

1.2- Inheritance Generics class

A class extended from a generics class  can specify parameter type for generics, retain generics parameters  or add  generics parameters .

Example 1:

PhoneNameEntry.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{

   // This class extends KeyValue<K,V>
   // And specify K, V
   // K = int (Phone Number)
   // V = string (Name)
   public class PhoneNameEntry : KeyValue<int, string>
   {

       public PhoneNameEntry(int key, string value)
           : base(key, value)
       {

       }

   }

}
Example use PhoneNameEntry:
PhoneNameEntryDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
  public class PhoneNameEntryDemo
  {

      public static void Main(string[] args)
      {

          PhoneNameEntry entry = new PhoneNameEntry(12000111, "Tom");

          // C# understands that the return type is int.
          int phone = entry.GetKey();

          // C# understands that the return type is string.
          string name = entry.GetValue();

          Console.WriteLine("Phone = " + phone + " / name = " + name);

          Console.Read();

      }

  }

}

Example 2:

StringAndValueEntry.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{

   // This class extends KeyValue<K,V>
   // Specify the parameter K is string.

   public class StringAndValueEntry<V> : KeyValue<string, V>
   {

       public StringAndValueEntry(string key, V value)
           : base(key, value)
       {
       }

   }

}
Example use StringAndValueEntry class:
StringAndValueEntryDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   public class StringAndValueEntryDemo
   {

       public static void main(String[] args)
       {

           // (Emp Number, Employee Name)
           // V = string (Employee Name)
           StringAndValueEntry<String> entry = new StringAndValueEntry<String>("E001", "Tom");

           String empNumber = entry.GetKey();

           String empName = entry.GetValue();

           Console.WriteLine("Emp Number = " + empNumber);
           Console.WriteLine("Emp Name = " + empName);

           Console.Read();

       }
   }

}

Example 3:

KeyValueInfo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{

  // This class extends KeyValue<K,V>
  // It has added a parameter generics I.
  public class KeyValueInfo<K, V, I> : KeyValue<K, V>
  {

      private I info;

      public KeyValueInfo(K key, V value)
          : base(key, value)
      {
      }

      public KeyValueInfo(K key, V value, I info)
          : base(key, value)
      {
          this.info = info;
      }

      public I GetInfo()
      {
          return info;
      }

      public void SetInfo(I info)
      {
          this.info = info;
      }

  }

}

1.3- Generics Interface

Interface with Generics:
GenericInterface.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
  public interface GenericInterface<G>
  {

      G DoSomething();

  }

}
For example, an implementation of the interface:
GenericInterfaceImpl.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{

   public class GenericInterfaceImpl<G> : GenericInterface<G>
   {

       private G something;

       public G DoSomething()
       {
           return something;
       }

   }

}

1.4- Using Generics with Exception

You can create an Exception with Generics parameters.
MyException.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   class MyException<E> : ApplicationException
   {

   }

}
Using the Generic Exception (Valid):
UsingGenericExceptionValid01.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   class UsingGenericExceptionValid01
   {

       public void SomeMethod()
       {
           try
           {
               // ...

           }
           // Valid
           catch (MyException<string> e)
           {
               // Do something here.
           }
           // Valid
           catch (MyException<int> e)
           {
               // Do something here.    
           }
           catch (Exception e)
           {
           }
       }

   }

}
Using the Generic Exception (Valid):
UsingGenericExceptionValid02.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   class UsingGenericExceptionValid02<K>
   {
       public void SomeMethod()
       {
           try
           {
               // ...

           }
           // Valid
           catch (MyException<string> e)
           {
               // Do something here.
           }
           // Valid
           catch (MyException<K> e)
           {
               // Do something here.      
           }
           catch (Exception e)
           {
           }
       }
   }

}
Using the Generic Exception (Invalid):
UsingGenericExceptionInvalid.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   class UsingGenericExceptionInvalid
   {
       public void SomeMethod()
       {
           try
           {
               // ...

           }
           // Valid
           catch (MyException<string> e)
           {
               // Do something here.
           }
           // Invalid (Unknown parameter K) ***********
           catch (MyException<K> e)
           {
               // Do something here.      
           }
           catch (Exception e)
           {
           }
       }
   }

}

2- Generic Methods

A method in class or Interface may be generified. 
MyUtils.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   public class MyUtils
   {
       // <K, V>: To say that this method has two parameters K, V
       // Method returns K.
       public static K GetKey<K, V>(KeyValue<K, V> entry)
       {
           K key = entry.GetKey();
           return key;
       }

       // <K, V>: To say that this method has two parameters K, V
       // Method returns V.
       public static V GetValue<K, V>(KeyValue<K, V> entry)
       {
           V value = entry.GetValue();
           return value;
       }

       // List <E>: The list contains the elements of type E
       // Method returns the type E.    
       public static E GetFirstElement<E>(List<E> list, E defaultValue)
       {
           if (list == null || list.Count == 0)
           {
               return defaultValue;
           }
           E first = list.ElementAt(0);
           return first;
       }

   }

}
For example, using generics method :
MyUtilsDemo.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   public class MyUtilsDemo
   {

       public static void Main(string[] args)
       {

           // K = int: Phone
           // V = string: Name
           KeyValue<int, string> entry1 = new KeyValue<int, String>(12000111, "Tom");
           KeyValue<int, string> entry2 = new KeyValue<int, String>(12000112, "Jerry");

           // (K = int).
           int phone = MyUtils.GetKey(entry1);
           Console.WriteLine("Phone = " + phone);

           // A list containing the element type KeyValue<int, string>.
           List<KeyValue<int, string>> list = new List<KeyValue<int, string>>();

           // Add element to list
           list.Add(entry1);
           list.Add(entry2);

           KeyValue<int, string> firstEntry = MyUtils.GetFirstElement(list, null);

           if (firstEntry != null)
           {
               Console.WriteLine("Value = " + firstEntry.GetValue());
           }

           Console.Read();
       }

   }

}
Running example:

3- Generic Object Initialization

Sometimes, you want to initialize a  Generic object:
public void DoSomething<T>()
{

     // Generic Object Initialization
     T t = new T(); // Error
}
 
The cause is T parameter is unlikely that it has constructor T(), so you need to add  when T: new() constraints. See the example below:
GenericInitializationExample.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   class GenericInitializationExample
   {

       // Type T must have default constructor.
       public void DoSomeThing<T>()
           where T : new()
       {
           T t = new T();
       }

       // Type T must have default constructor.
       // and extends KeyValue<K,string>
       public void ToDoSomeThing<K>()
           where K: KeyValue<K,string>, new( )
       {
           K key = new K();
       }


       public T DoDefault<T>()
       {
           // Return null if T is reference type
           // or return 0 if T is int, float,..
           return default(T);
       }
   }

}

4- Generic Array

In C # you can declare an array of generics:
// Initialize an array.

T[] myArray = new T[10];
GenericArrayExample.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GenericsTutorial
{
   class GenericArrayExample
   {

       public static T[] FilledArray<T>(T value, int count)
       {
           T[] ret = new T[count];
           for (int i = 0; i < count; i++)
           {
               ret[i] = value;
           }
           return ret;
       }

       public static void Main(string[] args)
       {

           string value = "Hello";

           string[] filledArray = FilledArray<string>(value, 10);

           foreach (string s in filledArray)
           {
               Console.WriteLine(s);
           }
       }
   }

}