Last time in our series about C# 7.3 language features, we covered tuple equality. Today we will look at unmanaged, delegate and enum type constraints. The latest Early Access Preview (EAP) versions of ReSharper 2018.2 and Rider 2018.2 come with language support for C# 7.3, do give them a try!
This post is part of a series (we will update the table of contents as we progress):
Ever since C# 2.0, when generics were introduced, it has been possible to set type constraints on generic parameters. For example, we could specify that the type constraint should implement a specific interface, and perhaps also have a parameterless constructor:
public class Repository<T>
where T : IEntity, new()
Generic type constraints can be added on class declarations, method declarations, and local functions. They allow us to constrain the types which can be used with the class or method we are creating.
Up until now, these constraints included reference type constraint (
class), value type constraint (
struct), interfaces or base class constraints, and whether a parameterless constructor should be present (
new()). With C# 7.3, three new generic type constraints are introduced (proposal):
(Fun fact: the CLR already supported these constraints, however the C# language prohibited using them. Jon Skeet has a blog post on making these constraints work in earlier C# versions.)
Let’s look at a simple example of the
Enum constraint and write a method that returns the string representations of all values in an
Enum. With the
System.Enum generic type constraint, we can ensure this method only gets called with an
Enum, and never with another type.
public static IEnumerable<string> GetValues<T>()
where T : struct, System.Enum
var enumType = typeof(T);
var items = Enum.GetValues(enumType);
foreach (var item in items)
yield return Enum.GetName(enumType, item);
(Note we also added the
struct, constraint here, to make sure
T can’t be the
System.Enum class itself – we want to prevent
GetValues<System.Enum>() from being called!)
Similarly, we can constrain generic classes and methods to
System.Delegate now as well. All types this constraint is defined on must be the same. So combining two delegates of the same type (example 1) will work fine, combining two delegates with different types (example 2) will fail to compile:
public static TDelegate Combine<TDelegate>(TDelegate source, TDelegate target)
where TDelegate : Delegate
return (TDelegate)Delegate.Combine(source, target);
// Example 1
void Hello() => Console.WriteLine("Hello");
Action world = () => Console.WriteLine("World");
var helloWorld = Combine(Hello, world);
// Example 2
Func<bool> test = () => true;
var example = Combine(test, world);
unmanaged constraint will probably be used less, it does come in handy for some developers, typically when authoring low-level libraries and frameworks.
In order to satisfy the
unmanaged constraint, a type must be a struct and all the fields of the type must fall into one of the following categories:
- Have the type
- Be an enum type.
- Be a pointer type.
- Be a user-defined
structthat satisfies the
For example, when writing a class or method that only works with unmanaged types, we’d normally have to create overloads for every type. We can now use the
unmanaged constraint instead, and then work with our value and declare pointers of unmanaged types, use the
sizeof operator, allocate arrays on the stack, pin heap-allocated data using the
fixed statement, and so on.
private static unsafe void DoSomething<T>(T value)
where T : unmanaged
// get size
int size = sizeof(T);
// get address and store it in pointer variable
T* a = &value;
// allocate array on stack
T* arr = stackalloc T;
// allocate array on heap and pin it
fixed (T* p = new T)
When we call this method using a managed type (e.g.
unmanaged constraint is not satisfied and code will not compile.
ReSharper and Rider come with a code inspection and quick fix that suggests adding an
unmanaged constraint. For example, when we try to declare a pointer to a managed type, code analysis will spot this and let us correct the issue using Alt+Enter.
Now let’s add a bit of geekiness and have a look at the Intermediate Language (IL) that is emitted by the C# compiler (ReSharper | Tools | IL Code). The CLR itself has no notion of the
unmanaged constraint – it only exists in C#.
To make the constraint work, C# adds an attribute on our type parameter (
System.Runtime.CompilerServices.IsUnmanagedAttribute), and emits a
modreq (“required modifier”) of type (
[mscorlib]System.Runtime.InteropServices.UnmanagedType) as well. By doing so, the compiler indicates that there are special semantics that should not be ignored. As such, compilers that do not emit this
modreq will be unable to satisfy this constraint.
It’s exciting to see some additions to the generic type system in .NET!