Generics repersent a significant enhancement introduced in C# 2.0 and the Common Language Runtime (CLR). This feature introduces the concept of type parameters to the .NET framwork, allowing developers to create classes and methods without specifying concrete types. Type parameters can be deferred until implementation in client code. This means when using a generic type parameter T, you can design a class like MyCollection<T> that client code can instantiate as MyCollection<int>, MyCollection<string>, or MyCollection<CustomClass>. This approach eliminates the performance costs and risks associated with runtime type casting or boxing operations.
Why Implement Generics?
When declaring a List<object> collection, you can add various data types such as integers, doubles, strings, and characters. However, when retrieving data, type conversions become necessary. These conversions can lead to exceptions and data precision loss, such as when converting a decimal to an integer. Additionally, boxing and unboxing opeartions degrade performance. Generics effectively address most of these issues.
Advantages of Generics:
- Code Reusability
Consider the following traditional approach that requires separate methods for different data types:
/// <summary>
/// Displays an integer value with type information
/// </summary>
/// <param name="number">Integer to display</param>
public static void DisplayInteger(int number)
{
Console.WriteLine("Method: {0}, Value: {1}, Type: {2}",
nameof(DisplayUtility), number, number.GetType().Name);
}
/// <summary>
/// Displays a string value with type information
/// </summary>
/// <param name="text">String to display</param>
public static void DisplayText(string text)
{
Console.WriteLine("Method: {0}, Value: {1}, Type: {2}",
nameof(DisplayUtility), text, text.GetType().Name);
}
/// <summary>
/// Displays a DateTime value with type information
/// </summary>
/// <param name="moment">DateTime to display</param>
public static void DisplayDateTime(DateTime moment)
{
Console.WriteLine("Method: {0}, Value: {1}, Type: {2}",
nameof(DisplayUtility), moment, moment.GetType().Name);
}
/// <summary>
/// Displays any object value with type information
/// While object serves as a base class for all types,
/// using it introduces two main problems:
/// 1. Boxing and unboxing operations
/// 2. Potential loss of data type precision
/// </summary>
/// <param name="item">Object to display</param>
public static void DisplayObject(object item)
{
Console.WriteLine("Method: {0}, Value: {1}, Type: {2}",
nameof(DisplayUtility), item.GetType().Name, item);
}
The limitations of this approach become apparent when you need to handle multiple data types. This is where generics demonstrate their value:
/// <summary>
/// Generic method that can handle any data type
/// The type parameter T is a placeholder that gets specified
/// when the method is called, allowing for type-safe operations
/// without boxing or unboxing
/// </summary>
/// <typeparam name="T">Type parameter</typeparam>
/// <param name="item">Item of type T to display</param>
public static void DisplayItem<T>(T item)
{
Console.WriteLine("Method: {0}, Value: {1}, Type: {2}",
nameof(DisplayUtility), item.GetType().Name, item);
}