C# Generic Constraints & collections

C# Generic Constraints

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.

Syntax:
GenericTypeName<T> where T  : contraint1, constraint2

The following example demonstrates a generic class with a constraint to reference types when instantiating the generic class.

Example: Declare Generic Constraints
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.

Example: struct Constraints
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.

Example: new() Constraint
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.

Example: BaseClass Constraint
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).

Sandip Mhaske

I’m a software developer exploring the depths of .NET, AWS, Angular, React, and digital entrepreneurship. Here, I decode complex problems, share insightful solutions, and navigate the evolving landscape of tech and finance.

Post a Comment

Previous Post Next Post