C# allows you to use constraints to restrict client code to specify certain types while instantiating generic types. It will give a compile-time error if you try to instantiate a generic type using a type that is not allowed by the specified constraints.
You can specify one or more constraints on the generic type using the where clause after the
generic type name.
GenericTypeName<T> where T : contraint1, constraint2
The following example demonstrates a generic class with a constraint to reference types when instantiating the generic class.
class DataStore<T> where T : class
{
public T Data { get; set; }
}
Above, we applied the class constraint, which means only a reference type can be passed as an argument while creating the DataStore class object. So, you can pass reference types such as class, interface, delegate, or array type. Passing value types will give a compile-time error, so we cannot pass primitive data types or struct types.
DataStore<string> store = new DataStore<string>(); // valid
DataStore<MyClass> store = new DataStore<MyClass>(); // valid
DataStore<IMyInterface> store = new DataStore<IMyInterface>(); // valid
DataStore<IEnumerable> store = new DataStore<IMyInterface>(); // valid
DataStore<ArrayList> store = new DataStore<ArrayList>(); // valid
//DataStore<int> store = new DataStore<int>(); // compile-time error
The following table lists the types of generic constraints.
| Constraint | Description |
|---|---|
| class | The type argument must be any class, interface, delegate, or array type. |
| class? | The type argument must be a nullable or non-nullable class, interface, delegate, or array type. |
| struct | The type argument must be non-nullable value types such as primitive data types int, char, bool, float, etc. |
| new() | The type argument must be a reference type which has a public parameterless constructor. It
cannot be combined with struct and unmanaged constraints. |
| notnull | Available C# 8.0 onwards. The type argument can be non-nullable reference types or value types. If not, then the compiler generates a warning instead of an error. |
| unmanaged | The type argument must be non-nullable unmanged types. |
| base class name | The type argument must be or derive from the specified base class. The Object, Array, ValueType classes are disallowed as a base class constraint. The Enum, Delegate, MulticastDelegate are disallowed as base class constraint before C# 7.3. |
| <base class name>? | The type argument must be or derive from the specified nullable or non-nullable base class |
| <interface name> | The type argument must be or implement the specified interface. |
| <interface name>? | The type argument must be or implement the specified interface. It may be a nullable reference type, a non-nullable reference type, or a value type |
| where T: U | The type argument supplied for T must be or derive from the argument supplied for U. |
where T : struct
The following example demonstrates the struct constraint that restricts type argument to be
non-nullable value type only.
class DataStore<T> where T : struct
{
public T Data { get; set; }
}
DataStore<int> store = new DataStore<int>(); // valid
DataStore<char> store = new DataStore<char>(); // valid
DataStore<MyStruct> store = new DataStore<MyStruct>(); // valid
//DataStore<string> store = new DataStore<string>(); // compile-time error
//DataStore<IMyInterface> store = new DataStore<IMyInterface>(); // compile-time error
//DataStore<ArrayList> store = new DataStore<ArrayList>(); // compile-time error
where T : new()
The following example demonstrates the struct constraint that restricts type argument to be
non-nullable value type only.
class DataStore<T> where T : class, new()
{
public T Data { get; set; }
}
DataStore<MyClass> store = new DataStore<MyClass>(); // valid
DataStore<ArrayList> store = new DataStore<ArrayList>(); // valid
//DataStore<string> store = new DataStore<string>(); // compile-time error
//DataStore<int> store = new DataStore<int>(); // compile-time error
//DataStore<IMyInterface> store = new DataStore<IMyInterface>(); // compile-time error
where T : baseclass
The following example demonstrates the base class constraint that restricts type argument to be
a derived class of the specified class, abstract class, or an interface.
class DataStore<T> where T : IEnumerable
{
public T Data { get; set; }
}
DataStore<ArrayList> store = new DataStore<ArrayList>(); // valid
DataStore<List> store = new DataStore<List>(); // valid
//DataStore<string> store = new DataStore<string>(); // compile-time error
//DataStore<int> store = new DataStore<int>(); // compile-time error
//DataStore<IMyInterface> store = new DataStore<IMyInterface>(); // compile-time error
//DataStore<MyClass> store = new DataStore<MyClass>(); // compile-time error
C# Generic & Non-generic Collections
C# includes specialized classes that store series of values or objects are called collections.
There are two types of collections available in C#: non-generic collections and generic collections.
The System.Collections namespace contains the non-generic collection types and
System.Collections.Generic namespace includes generic collection types.
In most cases, it is recommended to use the generic collections because they perform faster than non-generic collections and also minimize exceptions by giving compile-time errors.
Generic Collections
C# includes the following generic collection classes in the System.Collections.Generic
namespace.
| Generic Collections | Description |
|---|---|
| List<T> | Generic List<T> contains elements of specified type. It grows automatically as you add elements in it. |
| Dictionary<TKey,TValue> | Dictionary<TKey,TValue> contains key-value pairs. |
| SortedList<TKey,TValue> | SortedList stores key and value pairs. It automatically adds the elements in ascending order of key by default. |
| Queue<T> | Queue<T> stores the values in FIFO style (First In First Out). It keeps the order in which the values were added. It provides an Enqueue() method to add values and a Dequeue() method to retrieve values from the collection. |
| Stack<T> | Stack<T> stores the values as LIFO (Last In First Out). It provides a Push() method to add a value and Pop() & Peek() methods to retrieve values. |
| Hashset<T> | Hashset<T> contains non-duplicate elements. It eliminates duplicate elements. |
Non-generic Collections
| Non-generic Collections | Usage |
|---|---|
| ArrayList | ArrayList stores objects of any type like an array. However, there is no need to specify the size of the ArrayList like with an array as it grows automatically. |
| SortedList | SortedList stores key and value pairs. It automatically arranges elements in ascending order of key by default. C# includes both, generic and non-generic SortedList collection. |
| Stack | Stack stores the values in LIFO style (Last In First Out). It provides a Push() method to add a value and Pop() & Peek() methods to retrieve values. C# includes both, generic and non-generic Stack. |
| Queue | Queue stores the values in FIFO style (First In First Out). It keeps the order in which the values were added. It provides an Enqueue() method to add values and a Dequeue() method to retrieve values from the collection. C# includes generic and non-generic Queue. |
| Hashtable | Hashtable stores key and value pairs. It retrieves the values by comparing the hash value of the keys. |
| BitArray | BitArray manages a compact array of bit values, which are represented as Booleans, where true indicates that the bit is on (1) and false indicates the bit is off (0). |