Generics in .NET
Generics introduce the concept of parameterized types to the .NET Framework. This allows you to design classes, interfaces, methods, and delegates that operate on types specified by a type parameter. Generics provide a way to create reusable, type-safe components without sacrificing type safety or performance.
Why Use Generics?
Before generics, using collections like ArrayList required casting elements to their specific types, which could lead to runtime errors if the cast was incorrect. Generics address this by providing compile-time type checking.
Benefits of Generics:
- Type Safety: Generics enforce type constraints at compile time, preventing runtime casting errors.
- Performance: Generics avoid boxing and unboxing operations that occur with non-generic collections, leading to better performance.
- Code Reusability: You can write a single generic class or method that works with any type, reducing code duplication.
Generic Classes
A generic class is defined with a type parameter in angle brackets (<T>). The type parameter T acts as a placeholder for a type that will be specified when an instance of the class is created.
Example: A Generic Stack
using System;
using System.Collections.Generic;
public class Stack<T>
{
private List<T> _items = new List<T>();
public void Push(T item)
{
_items.Add(item);
}
public T Pop()
{
if (_items.Count == 0)
{
throw new InvalidOperationException("Stack is empty.");
}
T item = _items[_items.Count - 1];
_items.RemoveAt(_items.Count - 1);
return item;
}
public T Peek()
{
if (_items.Count == 0)
{
throw new InvalidOperationException("Stack is empty.");
}
return _items[_items.Count - 1];
}
public int Count
{
get { return _items.Count; }
}
}
You can then use this generic Stack class with specific types:
// Stack of integers
Stack<int> intStack = new Stack<int>();
intStack.Push(10);
intStack.Push(20);
int x = intStack.Pop(); // x will be 20
// Stack of strings
Stack<string> stringStack = new Stack<string>();
stringStack.Push("Hello");
stringStack.Push("World");
string s = stringStack.Pop(); // s will be "World"
Generic Methods
Generic methods are methods that have type parameters. They can be defined within generic or non-generic classes.
Example: A Generic Swap Method
using System;
public class Utilities
{
public static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
}
Usage:
int num1 = 5;
int num2 = 10;
Utilities.Swap(ref num1, ref num2); // num1 is now 10, num2 is now 5
string str1 = "A";
string str2 = "B";
Utilities.Swap(ref str1, ref str2); // str1 is now "B", str2 is now "A"
Type Constraints
You can specify constraints on the type parameters used in generic types and methods. Constraints limit the types that can be used as arguments for the type parameter, allowing you to call methods defined on the constrained type or to check for specific base types.
Common Constraints:
where T : struct: The type argument must be a non-nullable value type.where T : class: The type argument must be a reference type.where T : new(): The type argument must have a public, parameterless constructor.where T : <base class name>: The type argument must be or derive from the specified base class.where T : <interface name>: The type argument must implement the specified interface.
Example: Constraint on a Generic Class
public class DataProcessor<T> where T : IComparable<T>
{
public T FindMax(T item1, T item2)
{
// T must implement IComparable<T> to use CompareTo
if (item1.CompareTo(item2) > 0)
{
return item1;
}
return item2;
}
}
Generic Interfaces
Generic interfaces allow you to define interfaces that can be implemented by generic classes.
Example: Generic List Interface
The .NET Framework's System.Collections.Generic namespace provides many powerful generic interfaces and classes, such as IList<T>, IEnumerable<T>, Dictionary<TKey, TValue>, and more.
Further Reading
- MSDN Library: Generics
- https://docs.microsoft.com/en-us/dotnet/standard/generics