Generics in C# - Part II

Part I

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.

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 maybe 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.

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:

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 constraint also with generic methods

//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.


Recommended Ebook

Introduction to C# 8

Download Now!
Similar Articles