Generics in C# - Part II


Introduction:

Generics gives you the ability to create a generic methods or a generic type by defining a placeholder for method arguments or type definitions, which are specified at the time of invoking the generic method or creating the generic type.

Generic classes and structures

A generic class is a class that have a type parameter <T> representing the data type of the class variables.

Example:

public class MyGenericClass<T>

{

    //Generic variables

    private T d1;

    private T d2;

    private T d3;

  

    public MyGenericClass(){}

  

    //Generic constructor  

    public MyGenericClass(T a1, T a2, a3)

    {

        d1 = a1;

        d2 = a2;

        d3 = a3;

    }       

   

    //Generic properties

    public T D1

    {

        get{return d1; }

        set{d1 = value; }

    }

 

    public T D2

    {

        get{ return d2; }

        set{ d2 = value; }

    }

  

    public T D3

    {

        get{ return d3; }

        set{ d3 = value; }

    }

}
 
Assume you want a way to reset the variables in your generic class to its default values, but in fact you don't know really what is the data type that the placeholder <T> will represent, so you can't know what is the default value will be.
In generic classes you can make use of the default keyword to reset your data safely to its default value as follow:
 
d1 = default(T);
 
So whatever your type parameter will be, you can reset it safely to its default value.
 
We can add this method to our generic class:
 

public void ResetValues()

{

    d1 = default(T);

    d2 = default(T);

    d3 = default(T);

}

 

Here is our generic class in use:

 

//contain int values

MyGenericClass<int> c1 = new MyGenericClass<int>(5, 10, 15);

 

//reset values to 0

c1.ResetValues();

 

//contain string values

MyGenericClass<string> c2 = new MyGenericClass<string>("a", "b", "c");

 

//reset values to null

c2.ResetValues();


Note: you can create generic structures in the same way of creating generic classes, you only have to remove the class keyword and add struct keyword.
 

public struct MyGenericStruct<T>

{

}
 
Creating a Custom Generic Collection

Under the namespace System.Collections.Generic you will find a lot of generic collections types and may be you will not need to build a custom generic collection.
 
Building a custom generic collection allows you to build a generic collection that have your own methods and you can also constrain you generic collection to contain only the data type you want.
 
A generic collection is a generic class that implements the IEnumerable<T> generic interface. The IEnumerable<T> interface allows you to support foreach loop.
 
Example:

public class EmployeeCollection<T> : IEnumerable<T>

{

    List<T> empList = new List<T>();

   

    public void AddEmployee(T e)

    {

        empList.Add(e);

    }

 

    public T GetEmployee(int index)

    {

        return empList[index];

    }

 

    //Compile time Error

    public void PrintEmployeeData(int index)

    {

       Console.WriteLine(empList[index].EmployeeData);   

    }

 

    //foreach support

    IEnumerator<T> IEnumerable<T>.GetEnumerator()

    {

        return empList.GetEnumerator();

    }

 

    IEnumerator IEnumerable.GetEnumerator()

    {

        return empList.GetEnumerator();

    }

}

 

public class Employee

{

    string FirstName;

    string LastName;

    int Age;

   

    public Employee(){}

    public Employee(string fName, string lName, int Age)

    {

        this.Age = Age;

        this.FirstName = fName;

        this.LastName = lName;

    }

 

    public string EmployeeData

    {

        get {return String.Format("{0} {1} is {2} years old", FirstName, LastName, Age); }

    }

}
 
Constraining the type parameter of the generic collection:
 
When we try to use our Generic collection we will face some problems, let's see it and see how to solve it.
 
The first problem is that the user of the collection can use it for any data type instead of Employee:
 

EmployeeCollection<int> a = new EmployeeCollection<int>();

EmployeeCollection<string> a = new EmployeeCollection<string>();
 
The second problem will appear when you try to use the EmployeeData property of the Employee class in you generic collection, so if we add this method to our generic collection to use the EmployeeData property:
 

// Compile time Error

public void PrintEmployeeData(int index)

{

   Console.WriteLine(empList[index].EmployeeData);   

}
 
When we try to compile the generic collection we will get a compile time error because the identity of T is not known yet, and we don not know for certain if the type of the item in the List<T> has an EmployeeData property.
 
We can solve these problems by constraining the type parameter T using where as follow:


 

public class EmployeeCollection<T> : IEnumerable<T> where T : Employee

{

    //we can call EmployeeData property

    //because all items in the List<T> is Employee 

    public void PrintEmployeeData(int index)

    {

       Console.WriteLine(empList[index].EmployeeData);   

    }

}


We now can use the EmployeeData to contain only the Employee type:

 

//its OK

EmployeeCollection<Employee> a = new EmployeeCollection<Employee>();

 

//Compiler error

EmployeeCollection<int> a = new EmployeeCollection<int>();
 
There is other types of where constrain you can use:
 
Example:
 

where T : class

 

//The type parameter must be a reference type

public class MyGenericCollection<T> : IEnumerable<T> where T : class

{

    . . .

}

 

where T : struct

 

//The type parameter must be a value type

public class MyGenericCollection<T> : IEnumerable<T> where T : struct

{

    . . .

}

 

where T : new()

 

//The type parameter must have a default constructor

public class MyGenericCollection<T> : IEnumerable<T> where T : new()

{

    . . .

}

 

where T : InterfaceName 

 

//The type parameter must implement the interface

//specified by InterfaceName 

public class MyGenericCollection<T> : IEnumerable<T> where T : InterfaceName

{

    . . .

}

 

where T : ClassName

 

//The type parameter must be derived

//from the class specified by Classname

public class MyGenericCollection<T> : IEnumerable<T> where T : ClassName

{

    . . .

}
 
You can use the where constrain also with generic methods
 
Example:

//the arguments of this method must be value type

public void MyGenericMethod<T> (T x, T y) where T : struct

{

    . . .

}


Creating Generic Interfaces
 
Under the .NET 2.0 you can define generic interfaces, you can do this as follow:

public interface IMyGenericInterfce<T>

{

    void Method1(T a, T b);

    T Method2(T a, T b);

} 
 
You can use the constrains also with the generic interface:

//the type parameter must be a value type

public interface IMyGenericInterfce<T> where T : struct

{

    void Method1(T a, T b);

    T Method2(T a, T b);

}

Implementing our generic interface:

public class MyClass : IMyGenericInterfce<int>

{   

    public void Method1(int a, int b)

    {. . .}

 

    public int Method2(int a, int b)

    {. . .}

} 

Creating Generic Delegates
 
Under the .NET 2.0 you can define generic delegates, you can do this as follow:

//this delegate can call any method that return void

//and takes two parameters

public delegate void MyGenericDelegate<T>(T a, T b); 

Using our generic delegate:
 

MyGenericDelegate<int> myDel1 = new MyGenericDelegate<int>(MyTargetMethod1);

 

myDel1(5, 10);

 

MyGenericDelegate<string> myDel2 = new MyGenericDelegate<string>(MyTargetMethod2);

 

myDel2("a", "b");

 

public static void MyTargetMethod1(int x, int y)

{. . .}

 

public static void MyTargetMethod2(string x, string y)

{. . .}


I hope you know have a good idea about creating and using generic types.

Part I - Part II