How do I check if a type T
fits the unmanaged
type constraint, such that it could be used in a context like this: class Foo<T> where T : unmanaged
? My first idea was typeof(T).IsUnmanaged
or something similar, but that isn't a property/field of the Type
class
-
That's a type constraint for a generic. I'm asking for a given type `T`, at runtime, how can I check if it is unmanaged or not? – John Dec 29 '18 at 11:04
2 Answers
According to unmanaged
constraint documentations:
An unmanaged
type is a type that is not a reference type and doesn't contain reference type fields at any level of nesting.
Also it's mentioned in C# language design documentations about unmanaged type constraint:
In order to satisfy this 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
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,char
,float
,double
,decimal
,bool
,IntPtr
orUIntPtr
. - Be any
enum
type. - Be a pointer type.
- Be a user defined struct that satisfies the
unmanaged
constraint.
Considerations
Usually calling MakeGenericType
is the most reliable solution for validating generic type constraints which are enforced by CRL. Usually trying to implement validation by yourself is not a good idea because there may be a lot of rules which you should consider and there is always a chance for missing some of them. But be informed, at least at time of writing this answer, it's not working well for unmanaged
constraint.
.NET Core have a RuntimeHelpers.IsReferenceOrContainsReferences
but at the time of writing this answer, .NET Framework doesn't have such function. I should mention that even using IsReferenceOrContainsReferences
is not completely reliable for this task.
For example see the issue which I posted here about two structure which doesn't have any reference type but one of them evaluated as managed, one of them unmanaged (maybe a compiler bug).
Anyway, for now depending to your preference and requirements, use one of the following solutions to detect which type can satisfy unmanaged
generic type constraint.
Option 1 - Using MakeGenericType
As an option, to check if the type can satisfy the unmanaged
constraint, you can use the following IsUnmanaged
extension method'.
C# 7.3: It is supposed to be more reliable, but I should say, it's not. It seems for
unmanaged
constraint, CLR is not respecting the constraint and it's just a C# compiler feature. So at least for now, I recommend using the second option.
C# 8.0: Works as expected in C# 8.0
using System;
using System.Reflection;
public static class UnmanagedTypeExtensions
{
class U<T> where T : unmanaged { }
public static bool IsUnManaged(this Type t)
{
try { typeof(U<>).MakeGenericType(t); return true; }
catch (Exception){ return false; }
}
}
Option 2 - Writing your own method checking the documented rules
As another option, you can write your method checking documented rules for unmanaged
constraint. The following code has more rules rather than other answer to be able to handle cases like int?
or (int,int)
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
public static class UnmanagedTypeExtensions
{
private static Dictionary<Type, bool> cachedTypes =
new Dictionary<Type, bool>();
public static bool IsUnManaged(this Type t)
{
var result = false;
if (cachedTypes.ContainsKey(t))
return cachedTypes[t];
else if (t.IsPrimitive || t.IsPointer || t.IsEnum)
result = true;
else if (t.IsGenericType || !t.IsValueType)
result = false;
else
result = t.GetFields(BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance)
.All(x => x.FieldType.IsUnManaged());
cachedTypes.Add(t, result);
return result;
}
}
More Information
You may find the following links useful:
- Docs - Unmanaged constraint
- GitHub - C# 7.3 language design documents - Unmanaged type constraint
- A blog post by Sergey Teplyakov about Dissecting new generic constraints in C# 7.3
- A blog post by Maarten Balliauw about Unmanaged, delegate and enum type constraints
- GitHub Issue - Please clarify the implementation details of unmanaged generic constraints
- GitHub - Proposal: Unmanaged constructed types #1504

- 120,393
- 18
- 203
- 398
-
Answer as it much quicker than the other answer, even if slightly more complex than the other. Thanks for the answer :) – John Dec 29 '18 at 12:24
-
I also like the other answer, but to me, the code which is shared in my answer is more reliable and future proof as it relies on `unmanaged` constraints. – Reza Aghaei Dec 29 '18 at 12:27
-
My edit was refused [ :( ], but i suggest you include the early check of whether it is primitive/pointer/enum below (it must be unmanaged) and then if it isnt a value type (in which case it can't be managed). Saves the expensive reflection for simple cases. Also worth adding - in .NET Core there is `RuntimeHelpers.IsReferenceOrContainsReferences
()`, an inbuilt function for this purpose – John Dec 29 '18 at 19:14 -
@John I didn't see your edit, but will consider your comment and after some investigation I'll probably update the answer. Thanks for your comment :) – Reza Aghaei Dec 29 '18 at 19:16
-
Option 1 doesn't work. I just tested it on a struct that has a string field and it returned `true`. Because the CLR doesn't enforce the constraint, it won't throw an exception if you provide a match for the constraint it *can* check: `T : struct`. And if you inspect the constraint info, you'll see that it's *identical* for `T : struct` and `T : unmanaged`. The 'unmanaged' specifics are lost at runtime. Reminds me of generics in Java. – madreflection Jan 01 '19 at 06:34
-
@madreflection I see, at least at compile time `unmanaged` is more than `struct`, but at runtime it acts like `strucct` constraint. It's expected that all generic constraint be respected at run-time, At least C#/CLR used to do that. that's why I shared the first solution (at least to be more future-proof) .But it seems there are some CRL/Compiler bug/ about it. Cases like `int?` or `(int,int)` or structure containing "string" pushed me to the other solution. – Reza Aghaei Jan 01 '19 at 06:42
-
@madreflection During writing the code for the second option I faces with a strage behavior which is not limited to `unmanaged` constraint and I believe it's more related to tuple and the way that compiler/run-time treat it. I posted the [issue here](https://stackoverflow.com/q/53992855/3110834). The post is more about `unmanaged` constraint, but there is another problem in the post as well. Pointers. `(int,int)*` is not valid pointer, but having `struct MyStruct{(int,int) i;}` then `MyStruct*` is valid pointer! – Reza Aghaei Jan 01 '19 at 06:46
-
It's not a bug, it's by design, and documented quite clearly in the linked design document. C# 7.3, by introducing the added restrictions of the unmanaged constraint, made that expectation no longer entirely valid. The unmanaged constraint cannot be relied upon at runtime because it would have required CLR changes to support it at runtime and they didn't have the latitude to do so, so they did as much as they could do with just the compiler. – madreflection Jan 01 '19 at 06:59
-
Yes, they declared it as a feature of language. But the compiler implementation has some problems. See the [linked post](https://stackoverflow.com/q/53992855/3110834). – Reza Aghaei Jan 01 '19 at 07:05
-
1FYI - this doesn't work perfectly, and will return `false` for any pointer types at the moment. Pointer types don't inherit from `System.Object` and therefore do not inherit from `System.ValueType`, so `t.IsPointer` must be checked before `!t.IsValueType` to ensure correct results – John Jan 01 '19 at 10:20
-
@John, thanks, fixed. (But still is not perfect because of tuple case, which I asked in the linked post. It seems to have a robust solution for it, we should not rely on run-time. See [this post](https://github.com/dotnet/roslyn/issues/27474#issuecomment-394941170). – Reza Aghaei Jan 01 '19 at 11:26
I am not sure if something like this already exists, but you could implement your own extension method similar to:
public static bool IsUnmanaged(this Type type)
{
// primitive, pointer or enum -> true
if (type.IsPrimitive || type.IsPointer || type.IsEnum)
return true;
// not a struct -> false
if (!type.IsValueType)
return false;
// otherwise check recursively
return type
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
.All(f => IsUnmanaged(f.FieldType));
}
(update) For completeness, since recursion will be slow for structs with many nested members, the function can be made faster by caching the results:
private static readonly ConcurrentDictionary<Type, bool> _memoized =
new ConcurrentDictionary<Type, bool>();
public static bool IsUnmanaged(this Type type)
{
bool answer;
// check if we already know the answer
if (!_memoized.TryGetValue(type, out answer))
{
if (!type.IsValueType)
{
// not a struct -> false
answer = false;
}
else if (type.IsPrimitive || type.IsPointer || type.IsEnum)
{
// primitive, pointer or enum -> true
answer = true;
}
else
{
// otherwise check recursively
answer = type
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
.All(f => IsUnmanaged(f.FieldType));
}
_memoized[type] = answer;
}
return answer;
}

- 49,838
- 16
- 120
- 201
-
Thanks for the answer. I chose the other one, as while I think this one is prettier, it is much slower (my test got it as 10-20x slower), and it only gets slower with larger objects. – John Dec 29 '18 at 12:23
-
@John: hi, yes, the recursion if probably a killer here. I would assume that a cached/memoized version would be faster than the accepted one (i.e. one that uses a dictionary to store known results), especially since Reza's answer uses reflection (which is usually slow) and exceptions (which are slow -- although only if they are thrown). – vgru Dec 29 '18 at 13:31
-
@John: I've added the updated method, just to clarify what I meant. It should be as fast as the accepted answer, but it will use a bit more memory for the duration of the program (since the dictionary is static). I have used a ConcurrentDictionary to make the method thread safe, but if you know that this will be used from a single thread it can be made even faster using a plain dictionary. Of course, the accepted answer can use the same tactic to get the answer faster, if it's shown that it's needed. :) – vgru Dec 29 '18 at 14:05
-
this is not an O(1) algorithm, whereas the other is. This is, i think, O(n), as you need to check everything – John Dec 29 '18 at 14:07
-
@John: yes, this is `O(n)` on the first call, where `n` is the total number of fields within the struct (including all nested structs). Next call to this struct (and all members that were cached on the first call) is `O(1)`, likely faster than reflection, and very likely faster than the case when the original code throws. But the best way to make sure this is true is to call both functions a million times and compare the elapsed time. Calling it once or twice will certainly be slower. – vgru Dec 29 '18 at 14:25
-
The accepted answer is good because it is certain to work with all edge cases which my answer possibly might not cover. Perhaps the documentation isn't fully correct, perhaps I made a bug in my implementation; you won't have these problems with the accepted answer. And the other code is faster, unless you use memoization with the recursive approach. On the other hand, it uses `try`/`catch` as a method of checking whether instantiation will succeed, which is usually considered bad practice, but sometimes the only reliable way. It's possible a future version of .NET might include this property. – vgru Dec 29 '18 at 14:30
-
1@Groo I added more details to the answer because it seems `unmanaged` constraint is just supported by C# compiler not the run-time, so despite all other constraints, it's not respected by `MakeGenericType` method. So using a method like what you also have is an option (which is not perfect, because of exceptional case that I mentioned in my answer). And by the way, your code needs small fix, the method should return false when `t.IsGenericType`, it's because of a limitation in the current version of language. Anyway, you have my vote :) – Reza Aghaei Jan 01 '19 at 14:21