Generics in C# allow you to define classes, methods, and interfaces with a placeholder for the type of data they store or use. This flexibility enables you to write more general and reusable code. However, sometimes you need to restrict the types that can be used with generics to ensure certain operations are safe or possible. This is where generic constraints come into play.
In this article, we’ll explore the various constraints that can be applied to generic type parameters in C#, providing complete examples for each.
1. where T: struct
The struct constraint ensures that the type parameter is a value type. This is useful when you want to ensure the generic type is a non-nullable value type like int, float, or a custom struct.
public class GenericStruct<T> where T : struct
{
private T _value;
public GenericStruct(T value)
{
_value = value;
}
public T GetValue()
{
return _value;
}
public void SetValue(T value)
{
_value = value;
}
}
var genericStruct = new GenericStruct<int>(10);
Console.WriteLine($"Value: {genericStruct.GetValue()}"); // Output: Value: 10
2. where T: class
The class constraint ensures that the type parameter is a reference type. This is useful for ensuring the type is a class, interface, delegate, or array.
public class GenericClass<T> where T : class
{
private T _instance;
public GenericClass(T instance)
{
_instance = instance;
}
public T GetInstance()
{
return _instance;
}
public void SetInstance(T instance)
{
_instance = instance;
}
}
var genericClass = new GenericClass<string>("Hello");
Console.WriteLine($"Instance: {genericClass.GetInstance()}"); // Output: Instance: Hello
3. where T: new()
The new() constraint ensures that the type parameter has a public parameterless constructor. This is useful when you need to create instances of the generic type within the class.
public class GenericWithConstructor<T> where T : new()
{
private T _instance;
public GenericWithConstructor()
{
_instance = new T();
}
public T GetInstance()
{
return _instance;
}
}
var genericWithConstructor = new GenericWithConstructor<MyClass>();
Console.WriteLine($"Instance: {genericWithConstructor.GetInstance().Name}"); // Assuming MyClass has a Name property
4. where T: <base class>
This constraint ensures that the type parameter is a specific class or derives from a specific base class.
public class Animal { }
public class Dog : Animal { }
public class GenericWithBaseClass<T> where T : Animal
{
private T _animal;
public GenericWithBaseClass(T animal)
{
_animal = animal;
}
public T GetAnimal()
{
return _animal;
}
}
var genericWithBaseClass = new GenericWithBaseClass<Dog>(new Dog());
Console.WriteLine($"Animal: {genericWithBaseClass.GetAnimal()}"); // Output: Animal: Dog
Mariusz PostolPosted May 21, 2024, 9:03 AM
My understanding is that from the user of a generic type, it could be recognized as a restriction but from the type definition point of view it is narrowing the parameter meaning. The more precise definition of the parameter allows you better reuse its features. Do you agree?