Skip to content

Unmanaged constructed types C# 8.0performanceinterop

Allow a generic type built from unmanaged type arguments to satisfy the unmanaged constraint.

The unmanaged constraint C# 7.3 restricted a type parameter to types with no references at all, so the compiler could treat them as raw bytes. But it only accepted a type parameter, never a constructed generic type, so Wrapper<int> was rejected even though it contains nothing but an int.

C# 8.0 extends the rule. A constructed generic type satisfies unmanaged when every one of its type arguments does, recursively.

Code ​

csharp
struct Wrapper<T> where T : unmanaged
{
    public T Value;
    public int Tag;
}

// Wrapper<long> is unmanaged because long is
static int SizeOf<T>() where T : unmanaged => Unsafe.SizeOf<T>();

var size = SizeOf<Wrapper<long>>(); // 16
csharp
struct Wrapper<T> where T : unmanaged
{
    public T Value;
    public int Tag;
}

// Rejected: a constructed type never satisfied the constraint
// var size = SizeOf<Wrapper<long>>();

Notes ​

  • The constraint propagates: Wrapper<Wrapper<int>> is also unmanaged, because both levels are
  • A generic type with any reference-type field is never unmanaged, whatever its type arguments, because unmanaged means "contains no references anywhere in the layout"
  • This is what makes sizeof(T) legal over generic wrapper types, which previously required a non-generic struct per element type
  • sizeof(T) still needs an unsafe context or the sizeof operator's generic form, so Unsafe.SizeOf<T>() is the usual alternative in safe code
  • nint and nuint are unmanaged even though their size depends on the platform, so sizeof is evaluated at runtime for a generic parameter
  • See native integers C# 9.0 for the built-in pointer-sized types
  • See the spans, memory & allocation guide for working with this memory without allocating

More information ​

Powered by Intracia