C# where T : unmanaged: Generic Unmanaged Types
c# where t unmanaged: Learn how the C# where T : unmanaged constraint enables generic code with pointers, stackalloc, and sizeof, and when to use it.
c# where t unmanaged requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
The where T : unmanaged constraint in C# restricts a generic type parameter to unmanaged types. This constraint is essential for writing low-level code that works with pointers, stackalloc, and Span<T> when the underlying type is generic. Without it, the compiler cannot guarantee that a type parameter has a known size or that it is safe for pointer operations.
What Qualifies as an Unmanaged Type
An unmanaged type is a type that is not a reference type and contains no reference-type fields at any level. The set includes primitive numeric types, char, bool, enum types, pointers, and user-defined structs that contain only other unmanaged types. For example, int, double, MyStruct (if it contains only value types), and MyEnum all satisfy the constraint. Reference types, nullable value types, and structs that contain reference-type fields do not.
The constraint is available since C# 7.3. It implies the struct constraint, so a generic parameter declared with where T : unmanaged cannot be a reference type.
Declaring a Generic Method with the Constraint
Consider a method that converts any unmanaged value to a byte array. Without the constraint, you would need to use Marshal.SizeOf<T>() or reflection. With the constraint, you can use sizeof(T) directly in an unsafe context:
public static unsafe byte[] ToBytes<T>(T value) where T : unmanaged { byte[] bytes = new byte[sizeof(T)]; fixed (byte* p = bytes) { T* source = &value; Buffer.MemoryCopy(source, p, bytes.Length, sizeof(T)); } return bytes; }
The sizeof(T) expression is valid only because the compiler knows T is unmanaged. For a generic parameter without this constraint, sizeof is not allowed. The fixed statement pins the byte array so that the pointer can be used to copy the value.
Using stackalloc with Generic Types
stackalloc is another operation that requires a known size at compile time. With where T : unmanaged, you can allocate a buffer of T on the stack:
public static unsafe void Fill<T>(T value, int count) where T : unmanaged { T* buffer = stackalloc T[count]; for (int i = 0; i < count; i++) { buffer[i] = value; } }
The stackalloc expression uses the size of T to allocate the correct number of bytes. This is only possible because the constraint guarantees that T is a value type with a fixed size. If T were a reference type, the buffer would contain pointers, not the values themselves.
Combining with the Enum Constraint
The unmanaged constraint can be combined with Enum to create generic helpers for enum types. This is useful for parsing, conversion, or bit manipulation:
public static T Parse<T>(string input) where T : unmanaged, Enum { return (T)Enum.Parse(typeof(T), input); }
The Enum constraint ensures that T is an enum, and unmanaged ensures that it is a value type. Together they allow operations that require both characteristics. Without the unmanaged constraint, you would need to cast through object, which introduces boxing.
Runtime Behavior and Performance
The unmanaged constraint is enforced at compile time; there is no runtime type check. The JIT can generate specialized code for each unmanaged type, avoiding boxing and virtual dispatch. This can lead to better performance in hot paths, especially when combined with Span<T> and stackalloc. However, the actual speedup depends on the workload. The constraint also enables the use of sizeof and pointer arithmetic, which are essential for interop and high-performance data processing.
Common Mistakes and Limitations
One common mistake is assuming that all value types satisfy the constraint. A struct that contains a string field is not unmanaged, even though it is a value type. Another mistake is using the constraint with nullable value types; int? will cause a compile error because Nullable<T> is not considered unmanaged. The constraint is also not satisfied by interfaces or abstract types.
Another limitation is that the unmanaged constraint does not guarantee that the type is blittable for interop. A struct with explicit layout and unmanaged fields is blittable, but the constraint alone does not ensure that. For interop, you may still need to use [StructLayout] attributes.
When to Use the Constraint
Use where T : unmanaged when you need to perform pointer arithmetic, use stackalloc, or call sizeof on a generic type. It is also valuable when you want to avoid boxing in generic algorithms that work with value types. If you only need the struct constraint, prefer that because it is less restrictive. The unmanaged constraint should be used only when the additional guarantees are required.