C# Generics
Generics allow you to defer the specification of the data type of programming elements in a class or a method until it is actually used in the program. In other words, generics allow you to write a class or method that can work with any data type.
You can write a specification for a class or a method with substitute parameters for data types. When the compiler encounters a constructor for a class or a method call, it generates code to handle the specified data type. The following simple example will help you understand the concept:
Example
using System;
using System.Collections.Generic;
namespace GenericApplication
{
public class MyGenericArray<T>
{
private T[] array;
public MyGenericArray(int size)
{
array = new T[size + 1];
}
public T getItem(int index)
{
return array[index];
}
public void setItem(int index, T value)
{
array[index] = value;
}
}
class Tester
{
static void Main(string[] args)
{
// Declare an integer array
MyGenericArray<int> intArray = new MyGenericArray<int>(5);
// Set values
for (int c = 0; c < 5; c++)
{
intArray.setItem(c, c*5);
}
// Get values
for (int c = 0; c < 5; c++)
{
Console.Write(intArray.getItem(c) + " ");
}
Console.WriteLine();
// Declare a character array
MyGenericArray<char> charArray = new MyGenericArray<char>(5);
// Set values
for (int c = 0; c < 5; c++)
{
charArray.setItem(c, (char)(c+97));
}
// Get values
for (int c = 0; c < 5; c++)
{
Console.Write(charArray.getItem(c) + " ");
}
Console.WriteLine();
Console.ReadKey();
}
}
}
When the above code is compiled and executed, it produces the following result:
0 5 10 15 20
a b c d e
Features of Generics
Using generics is a technique that enhances the functionality of your program, specifically in the following ways:
It helps you to maximize code reuse, type safety, and performance.
You can create generic collection classes. The .NET Framework class library contains several new generic collection classes in the System.Collections.Generic namespace. You can use these generic collection classes instead of the collection classes in System.Collections.
You can create your own generic interfaces, generic classes, generic methods, generic events, and generic delegates.
You can constrain generic classes to access specific data type methods.
Information about the types used in a generic data type is available at runtime through reflection.
Generic Methods
In the above example, we have used a generic class, and we can declare a generic method with type parameters. The following program illustrates this concept:
Example
using System;
using System.Collections.Generic;
namespace GenericMethodApplication
{
class Program
{
static void Swap<T>(ref T lhs, ref T rhs)
{
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}
static void Main(string[] args)
{
int a = 1;
int b = 2;
Swap<int>(ref a, ref b);
Console.WriteLine("a = {0}, b = {1}", a, b);
}
}
}
namespace GenericMethodAppl
{
class Program
{
static void Swap<T>(ref T lhs, ref T rhs)
{
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}
static void Main(string[] args)
{
int a, b;
char c, d;
a = 10;
b = 20;
c = 'I';
d = 'V';
// Display values before swap
Console.WriteLine("Int values before calling swap:");
Console.WriteLine("a = {0}, b = {1}", a, b);
Console.WriteLine("Char values before calling swap:");
Console.WriteLine("c = {0}, d = {1}", c, d);
// Call swap
Swap<int>(ref a, ref b);
Swap<char>(ref c, ref d);
// Display values after swap
Console.WriteLine("Int values after calling swap:");
Console.WriteLine("a = {0}, b = {1}", a, b);
Console.WriteLine("Char values after calling swap:");
Console.WriteLine("c = {0}, d = {1}", c, d);
Console.ReadKey();
}
}
}
When the above code is compiled and executed, it produces the following result:
Int values before calling swap:
a = 10, b = 20
Char values before calling swap:
c = I, d = V
Int values after calling swap:
a = 20, b = 10
Char values after calling swap:
c = V, d = I
Generic Delegates
You can define a generic delegate with type parameters. For example:
delegate T NumberChanger<T>(T n);
The following example demonstrates the use of delegates:
Example
using System;
using System.Collections.Generic;
delegate T NumberChanger<T>(T n);
namespace GenericDelegateAppl
{
class TestDelegate
{
static int num = 10;
public static int AddNum(int p)
{
num += p;
return num;
}
public static int MultNum(int q)
{
num *= q;
return num;
}
public static int getNum()
{
return num;
}
static void Main(string[] args)
{
// Create delegate instances
NumberChanger<int> nc1 = new NumberChanger<int>(AddNum);
NumberChanger<int> nc2 = new NumberChanger<int>(MultNum);
// Calling the methods using the delegate objects
nc1(25);
Console.WriteLine("Value of Num: {0}", getNum());
nc2(5);
Console.WriteLine("Value of Num: {0}", getNum());
Console.ReadKey();
}
}
}
nc1(25);
Console.WriteLine("Value of Num: {0}", getNum());
nc2(5);
Console.WriteLine("Value of Num: {0}", getNum());
Console.ReadKey();
}
}
}
When the above code is compiled and executed, it produces the following result:
Value of Num: 35
Value of Num: 175