如何检查类型是否符合 C# 中的非托管约束?

How do I check if a type fits the unmanaged constraint in C#?

如何检查类型 T 是否符合 unmanaged 类型约束,以便它可以在这样的上下文中使用:class Foo<T> where T : unmanaged?我的第一个想法是 typeof(T).IsUnmanaged 或类似的东西,但这不是 Type class

的 property/field

根据unmanaged约束文档:

unmanaged 类型不是引用类型,并且在任何嵌套级别都不包含引用类型字段。

C#语言设计文档中也提到了unmanaged type constraint:

为了满足此约束,类型必须是结构并且该类型的所有字段必须属于以下类别之一:

  • 具有类型 sbytebyteshortushortintuintlongulongcharfloatdoubledecimalboolIntPtrUIntPtr.
  • 是任何enum类型。
  • 是指针类型。
  • 是满足unmanaged约束的用户定义结构。

注意事项

通常调用 MakeGenericType 是验证 CRL 强制执行的通用类型约束的最可靠解决方案。通常尝试自己实施验证不是一个好主意,因为可能有很多规则需要考虑,并且总是有可能遗漏其中一些规则。但请注意,至少在撰写此答案时,它不适用于 unmanaged 约束。

.NET Core 有一个 RuntimeHelpers.IsReferenceOrContainsReferences 但是在写这个答案的时候,.NET Framework 没有这样的功能。我应该提一下,即使使用 IsReferenceOrContainsReferences 也不能完全可靠地完成这项任务。

例如,请参阅 关于两个没有任何引用类型的结构,但其中一个被评估为托管类型,另一个被评估为非托管(可能是编译器错误)。

无论如何,现在根据您的偏好和要求,使用以下解决方案之一来检测哪种类型可以满足 unmanaged 通用类型约束。

选项 1 - 使用 MakeGenericType

作为一个选项,检查类型是否可以满足unmanaged约束,你可以使用下面的IsUnmanaged扩展方法'。

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; }
    }
}

选项 2 - 编写自己的方法检查记录的规则

作为另一种选择,您可以编写方法来检查 unmanaged 约束的记录规则。下面的代码有更多的规则而不是其他答案能够处理像 int?(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;
    }
}

更多信息

您可能会发现以下链接有用:

我不确定这样的东西是否已经存在,但您可以实现自己的扩展方法,类似于:

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));
}

(更新)为了完整起见,由于具有许多嵌套成员的结构的递归速度很慢,因此可以通过缓存结果使函数更快:

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;
}