C# Generics allows for the creation of reusable, type-safe code. Generic allow for the creation of classes, interfaces, and methods that can work with any data type, rather than being tied to a specific type. This makes it possible to create more flexible and reusable code.
// Define a generic method
public static void PrintData<T>(T data)
{
Console.WriteLine("Data :" + data);
}
//Use the generic method with a string data type
PrintData<String>("Hello Rakesh");
//Use the generic method with an integer data type
PrintData<int>(101);
When to use Generics ?
Generics are useful when you need to create code that can work with different types of data. They can be used to create reusable components such as collections, algorithms, and data structures that can be customized to work with different types.
Examples:
- A collection class that can hold objects of any type
- An algorithm that works on numbers, strings, or any other type of data
- A data structure that can be used to store any type of data
Constraints in C# Generics
Constraints in C# Generics are a way to restrict the types that can be used with a generic type or method. For example, if we want to ensure that a generic type or method can only accept reference types, we can add the “class” constraint.
public class MyClass<T> where T : class
{
// class code here
}
this ensures that only reference types can be used for the type parameter T. other constraints include “struct” (which restricts the type of value types), “new()” – which requires the type to have a public parameter less constructor, and “base class name” – which requires the type to derive from a specific base class.
interface IAnimalContainer<out T>
{
T GetAnimal();
}
in the above code, we have a generic interface IAnimalContainer that has a method GetAnimal that returns on instance of type “Animal”
Benefits and Limitations of C# Generics
- Type Safety: Generics provide compile-time type safety instead of relying on runtime casting and conversion.
- Reusability: Generic classes and methods reduce duplicated code by being reusable across different types and data structures
- Performance: Generics can improve performance by eliminating runtime type checking, boxing, and unboxing operations.
The limitations of C# Generics include:
- Complex Code: Generics can make code harder to understand and maintain
- Limited constraints: Constraints on generic types are limited, making it difficult to enforce rules on argument types
- Limited covariance/contravariance: Some types can’t be used in a covariant or contravariant manner, limiting flexibility.
Using Generics with Collections
Generics are often used with collections in C#. The System.Collections.Generic namespace provides a set of generic collection classes that can be used to store and manipulate data of a specific type.
for example, the List<T> class can be used to store a list of items of a specific type:
List<int> numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
the Dictionary<TKey, TValue> class can be used to store a set of key-value pairs of specific types:
Dictionary<string, string> names = new Dictionary<string, string>();
names.Add("Rakhi", "U");
names.Add("Raki", "U");
other generic collection classes include Stack<T>, Queue<T>, HashSet<T>, and LinkedList<T>.
Generic Delegates in C#
Delegates are used to define a type-safe function pointer. In C#, generic delegates can be used to define a type-safe function pointer that can work with any type.
For example, the Func<T> delegate is a generic delegate that can be used to define a function that returns a value of type T:
Func<int> getInt = () => 14;
int myInt = getInt();
The Action<T> delegate is a generic delegate that can be used to define a function that takes a parameter of type T and returns void
Action<int> printInt = (x) => Console.WriteLine(x);
printInt(101);
Generic Interfaces in C#
In C#, interfaces can also be made generic. This allows us to define an interface that can work with any type.
for example, the IComparable<T> interface can be used to define a comparison method that can work with any type:
interface IComparable<T>
{
int CompareTo(T other);
}
The IEnumerable<T> interface cab be used to define an interface for an object that can be enumerated
interface IEnumerable<out T>
{
IEnumerator<T> GetEnumerator();
}