How do I check if a type fits the unmanaged constr

2020-03-01 09:36发布

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

2条回答
Anthone
2楼-- · 2020-03-01 10:18

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;
}
查看更多
何必那么认真
3楼-- · 2020-03-01 10:21

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 or UIntPtr.
  • 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:

查看更多
登录 后发表回答