Generics give you the ability to create generic methods or a generic type by defining a placeholder for method arguments or type definitions, that are specified at the time of invoking the generic method or creating the generic type.
To understand the importance of generics let's start by seeing some kind of problems that can be solved by them.
Let's start by creating an ArrayList to hold stack-allocated data.
ArrayList intList = new ArrayList();
As you may know, the ArrayList collection can receive and return only an Object type, so the runtime will convert the value type (stack-allocated) automatically via boxing operation into an Object (heap-allocated) as in the following:
ArrayList intList = new ArrayList();
//add a value type to the collection(boxing)
intList.Add(5);
To retrieve the value from the ArrayList you must unbox the heap-allocated object into a stack-allocated integer using a casting operation.
//unboxing
int x = (int)intList[0];
The problem with the stack/heap memory transfer is that it can have a big impact on performance of your application because when you use boxing and unboxing operations the following steps occur:
- A new object must be allocated on the managed heap.
- The value of the stack-allocated type must be transferred into that memory location.
- When unboxed, the value stored on the heap must be transferred back to the stack.
- The unused object on the heap will be garbage collected.
Now consider that your ArrayList contained thousands of integers that are manipulated by your program, this for sure will have an affect on your application performance.
Custom Collections
Assume that you need to create a custom collection that can only contain objects of the Employee type.
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 override string ToString()
{
return String.Format("{0} {1} is {2} years old", FirstName, LastName, Age);
}
}
Now we will build the custom collection as in the following:
public class EmployeesCollection : IEnumerable
{
ArrayList alEmployees = new ArrayList();
public EmployeesCollection() { }
//Insert Employee type
public void AddEmployee(Employee e)
{
//boxing
alEmployees.Add(e);
}
//get the employee type
public Employee GetEmployee(int index)
{
//unboxing
return (Employee)alEmployees[index];
}
//to use foreach
IEnumerator IEnumerable.GetEnumerator()
{
return alEmployees.GetEnumerator();
}
}
The problem here is that if you have many types in you application then you need to create multiple custom collections, one for each type. And as you can see, we also have the problem of boxing and unboxing here.
All problems you saw previously can be solved using generics, so let's see what we can do.
The System.Collections.generic namespace
You can find many generic collections in the System.Collections.Generic just like:
-
List<T>
-
Dictionary<K, V>
-
Queue<T>
-
Stack<T>
Generic collections allow you to delay the specification of the contained type until the time of creation.
By using the generic collection you avoid performance problems of the boxing and unboxing operations and don't need to create a custom collection for each type in you application.
With the generic collections it's up to you to define the type that will be contained in the collection by replacing the placeholder T by the type you want at the time of creation.
List<T>
The List<T> is a generic collection that represents a strongly typed list of objects that can be accessed by index. It is just like the non-generic collection ArrayList.
The following is an example of a List<T>:
//Can only contain int type
List<int> intList = new List<int>();
//no boxing
intList.Add(10);
//no unboxing
int x = intList[0];
//Can only contain Employee objects
List<Employee> empList = new List<Employee>();
//no boxing
empList.Add(new Employee("Amr", "Ashush", 23));
//no unboxing
Employee e = empList[0];
Queue<T>
Queue<T> is a generic collection that represents a first-in, first-out (FIFO) collection of objects. It is just like the non-generic collection Queue.
The following is an example of a Queue<T>:
//A generic Queue collection
Queue<int> intQueue = new Queue<int>();
//Add an int to the end of the queue
//(no boxing)
intQueue.Enqueue(5);
//Returns the int at the beginning of the queue
//without removing it.
//(no unboxing)
int x = intQueue.Peek();
//Removes and returns the int at the beginning of the queue
//(no unboxing)
int y = intQueue.Dequeue();
Stack<T>
Stack<T> is a generic collection that represents a last-in-first-out (LIFO) collection of instances of the same arbitrary type. It is just like the non-generic collection Stack.
The following is an example of a Stack<T>:
Stack<int> intStack = new Stack<int>();
//Insert an int at the top of the stack
//(no boxing)
intStack.Push(5);
//Returns the int at the top of the stack
//without removing it.
//(no unboxing)
int x = intStack.Peek();
//Removes and returns the int at the top of the stack
//(no unboxing)
int y = intStack.Pop();
Dictionary<K, V>
Dictionary<K, V> is a generic collection that contains data in Key/Value pairs, it is just like the non-generic collection Hashtable.
The following is an example of a Dictionary<K, V>:
Dictionary<string, string> dictionary = new Dictionary<string, string>();
//Add the specified key and value to the dictionary
dictionary.Add("Key", "Value");
//Removes the value with the specified key from the dictionary
dictionary.Remove("Key");
//get the number of the Key/Value pairs contained in the dictionary
int count = dictionary.Count;
Generic Methods
You can create generic methods that can operate on any possible data type.
To define a generic method you specify the type parameter after the method name and before the parameters list.
The following is an example of a generic method:
public void MyGenericMethod<T>(T x, T y)
{
Console.Writeline("Parameters type is {0}", typeof(T));
}
You can define the type you want at the time you invoke the method.
int x, y;
MyGenericMethod<int>(x, y);
The result will be a ???.
The parameter type is a System.Int32.
string x, y;
MyGenericMethod<string>(x, y);
The result will be a ???.
The parameter type is a System.String.
You can also create a generic method without parameters as follows:
public void MyGenericMethod<T>()
{
T x;
Console.WriteLine("The type of x is {0}", typeof(T));
}
Here we see the method in use:
MyGenericMethod<int>();
The result will be:
The type of x is a System.Int32.
MyGenericMethod<string>();
The result will be:
The type of x is a System.String.
Note: you can omit the type parameter if the generic method requires arguments, because the compiler can infer the type parameter based on the member parameters. However if your generic method doesn't take any parameters then you are required to supply the type parameter or you will have a compile error.
Example:
//a generic method that take two parameters
public void MyGenericMethod<T>(T x, T y)
{
......
}
......
string x, y;
//the compiler here will infer the type parameter
MyGenericMethod(x, y)
In the case of a generic method that doesn't take parameters
//a generic method that doesn't take parameters
public void MyGenericMethod<T>()
{
......
}
//you must supply the type parameter
MyGenericMethod<string>();
//you will have a compiler error here
MyGenericMethod();
In Part II you will see how to create generic classes, structures, delegates, interfaces and you will learn how to create a custom generic collection.