Generics in C# provide a powerful way to create reusable, type-safe classes, methods, delegates, and interfaces. They enable developers to define classes and methods with placeholders for data types, reducing code duplication and improving performance. This step-by-step guide will cover how to declare and use generic classes in C# effectively, exploring advanced use cases and best practices.
What Are Generics in C#?
Generics allow developers to create classes, methods, and data structures that operate on any data type while maintaining strong type safety. This eliminates the need for type casting and improves performance by avoiding boxing and unboxing.
Key Benefits of Generics:
Type Safety: Ensures that only the intended data type is used.
Performance: Reduces runtime overhead by eliminating boxing/unboxing.
Code Reusability: Helps in writing more generic and reusable code.
Better Maintainability: Encourages clean and modular code.
Declaring a Generic Class in C#
A generic class is declared using angle brackets (<>
) to specify type parameters. Here’s a basic example:
public class GenericClass<T>
{
private T _value;
public GenericClass(T value)
{
_value = value;
}
public T GetValue()
{
return _value;
}
}
Explanation:
T
is a type parameter, meaning it can be replaced by any data type at runtime._value
is a private field of typeT
.The constructor initializes
_value
.The
GetValue
method returns the stored value of typeT
.
Using a Generic Class
Let’s instantiate our generic class with different data types:
class Program
{
static void Main()
{
GenericClass<int> intInstance = new GenericClass<int>(10);
Console.WriteLine(intInstance.GetValue()); // Output: 10
GenericClass<string> stringInstance = new GenericClass<string>("Hello Generics");
Console.WriteLine(stringInstance.GetValue()); // Output: Hello Generics
}
}
Constraints in Generic Classes
Generic classes can be restricted to specific data types using constraints. Constraints help enforce type safety by limiting the types that can be used as type parameters.
Common Generic Constraints:
where T : struct
- Only value types allowedwhere T : class
- Only reference types allowedwhere T : new()
- Must have a parameterless constructorwhere T : BaseClass
- Must inherit fromBaseClass
where T : interface
- Must implement a specific interface
Example with Constraints:
public class GenericRepository<T> where T : class, new()
{
private List<T> _items = new List<T>();
public void Add(T item)
{
_items.Add(item);
}
public List<T> GetAll()
{
return _items;
}
}
Multiple Type Parameters in Generic Classes
A generic class can take multiple type parameters:
public class KeyValueStore<TKey, TValue>
{
private Dictionary<TKey, TValue> _store = new Dictionary<TKey, TValue>();
public void Add(TKey key, TValue value)
{
_store[key] = value;
}
public TValue Get(TKey key)
{
return _store.ContainsKey(key) ? _store[key] : default;
}
}
Using Multiple Type Parameters:
KeyValueStore<int, string> store = new KeyValueStore<int, string>();
store.Add(1, "One");
Console.WriteLine(store.Get(1)); // Output: One
Generic Interfaces
Generic interfaces allow defining common behaviors for different types. For example:
public interface IRepository<T>
{
void Add(T item);
List<T> GetAll();
}
public class Repository<T> : IRepository<T> where T : class
{
private List<T> _items = new List<T>();
public void Add(T item)
{
_items.Add(item);
}
public List<T> GetAll()
{
return _items;
}
}
Covariance and Contravariance in Generics
Covariance (
out
keyword) allows a generic type to return a more derived type.Contravariance (
in
keyword) allows a generic type to accept a less derived type.
Example:
public interface ICovariant<out T>
{
T GetItem();
}
public interface IContravariant<in T>
{
void SetItem(T item);
}
Best Practices for Using Generics
Use meaningful type parameter names (e.g.,
T
,TKey
,TValue
).Apply constraints wisely to enforce type safety.
Avoid excessive generic nesting to maintain readability.
Use generic interfaces for dependency injection in ASP.NET Core applications.
Leverage generic methods instead of generic classes when appropriate.
Conclusion
Generics in C# provide a powerful way to create type-safe, reusable, and efficient code. By understanding how to declare and utilize generic classes, developers can write more flexible and maintainable applications. Whether you’re building a simple data structure or an advanced repository pattern in ASP.NET Core, generics are an essential tool in modern C# development.