如何确定泛型类型参数的可空性 (NRT)?

发布于 2025-01-16 09:14:40 字数 293 浏览 1 评论 0原文

例如,我可能有一个具有以下签名的方法:

public async Task<ViewModel?> GetPersonUri()

使用反射我想确定 Task 的类型参数是否为可为 null 的引用类型。在这种情况下,答案是肯定的。如果是:

public async Task<ViewModel> GetPersonUri()

答案应该是否定的。

如何确定泛型类型参数的可为空性?

For example, I might have a method with the following signature:

public async Task<ViewModel?> GetPersonUri()

Using reflection I would like to determine if the type parameter of Task is a nullable reference type. In the case, the answer would be yes. If it were:

public async Task<ViewModel> GetPersonUri()

the answer should be no.

How can I determine the nullability of a generic type parameter?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

故事未完 2025-01-23 09:14:40

在 .NET 6 开始中,我们获得了 NullabilityInfoContext 类型,请参阅此答案< /a> 了解详细信息。

在此之前,您必须自己手动检查属性。这似乎可以解决问题:

public static bool GenericReturnTypeIsNullable(MethodInfo methodInfo)
{
    var nullableAttribute = methodInfo!.ReturnTypeCustomAttributes.GetCustomAttributes(true)
        .FirstOrDefault(x => x.GetType().FullName == "System.Runtime.CompilerServices.NullableAttribute");
    if (nullableAttribute != null)
    {
        var flagsField = nullableAttribute.GetType().GetField("NullableFlags");
        var flags = (byte[]?)flagsField?.GetValue(nullableAttribute);
        return flags != null && flags.Length >= 2 && flags[1] == 2;
    }
    
    if (CheckNullableContext(methodInfo.CustomAttributes))
        return true;
    
    for (var type = methodInfo.DeclaringType; type != null; type = type.DeclaringType)
    {
        if (CheckNullableContext(type.CustomAttributes))
            return true;
    }
    
    static bool CheckNullableContext(IEnumerable<CustomAttributeData> customAttributes)
    {
        var context = customAttributes.FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableContextAttribute");
        return context != null &&
            context.ConstructorArguments.Count == 1 &&
            context.ConstructorArguments[0].ArgumentType == typeof(byte) &&
            (byte)context.ConstructorArguments[0].Value! == 2;
    }

    return false;
}

在 dotnetfiddle.net 上查看

有关详细信息,请参阅此规范文档

我们最终需要检查很多事情。

如果该方法的返回值具有 Nullable 属性,则其 NullableFlags 字段是一个字节数组,其中数组的每个成员都引用连续的泛型类型。 0 表示忽略 null,1 表示不可为 null,2 表示可为 null。因此:

[return: System.Runtime.CompilerServices.Nullable(new byte[] { 1, 2 })]
public Task<string?> Foo() => ...

1 引用 Task 本身,并指示它不可为空。 2 引用第一个泛型类型参数,即string?,并指示它可以为空。

如果此属性不存在,我们需要检查方法本身的 NullableContext 属性。这为没有指定 Nullable 属性的所有内容提供了默认的可为空性。这需要一个字节,同样是 02

如果该方法没有此属性,则它可能位于包含类型或其包含类型上。继续向上寻找,直到找到(或找不到)。

稍微棘手的一点是编译器将 Nullable 和 NullableContext 属性直接发出到程序集中,这意味着每个程序集都有自己的定义。因此,我们需要使用反射来访问它们 - 我们无法将任何内容转换为 NullableAttributeNullableContextAttribute

In .NET 6 onwards, we gained the NullabilityInfoContext type, see this answer for details.

Prior to this, you have to manually inspect the attributes yourself. This seems to do the trick:

public static bool GenericReturnTypeIsNullable(MethodInfo methodInfo)
{
    var nullableAttribute = methodInfo!.ReturnTypeCustomAttributes.GetCustomAttributes(true)
        .FirstOrDefault(x => x.GetType().FullName == "System.Runtime.CompilerServices.NullableAttribute");
    if (nullableAttribute != null)
    {
        var flagsField = nullableAttribute.GetType().GetField("NullableFlags");
        var flags = (byte[]?)flagsField?.GetValue(nullableAttribute);
        return flags != null && flags.Length >= 2 && flags[1] == 2;
    }
    
    if (CheckNullableContext(methodInfo.CustomAttributes))
        return true;
    
    for (var type = methodInfo.DeclaringType; type != null; type = type.DeclaringType)
    {
        if (CheckNullableContext(type.CustomAttributes))
            return true;
    }
    
    static bool CheckNullableContext(IEnumerable<CustomAttributeData> customAttributes)
    {
        var context = customAttributes.FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableContextAttribute");
        return context != null &&
            context.ConstructorArguments.Count == 1 &&
            context.ConstructorArguments[0].ArgumentType == typeof(byte) &&
            (byte)context.ConstructorArguments[0].Value! == 2;
    }

    return false;
}

See it on dotnetfiddle.net.

See this spec doc for details.

We end up needing to check a number of things.

If the method has a Nullable attribute on its return value, its NullableFlags field is a byte array, where each member of the array refers to successive generic types. A 0 means null-oblivious, 1 means not nullable, 2 means nullable. So in:

[return: System.Runtime.CompilerServices.Nullable(new byte[] { 1, 2 })]
public Task<string?> Foo() => ...

The 1 refers to the Task<T> itself, and indicates that it's not nullable. The 2 refers to the first generic type parameter, i.e. the string?, and indicates that it is nullable.

If this attribute doesn't exist, we need to check for a NullableContext attribute on the method itself. This provides a default nullability for everything which doesn't have a specified Nullable attribute. This takes a single byte, again 0 to 2.

If the method doesn't have this attribute, it might be on the containing type, or its containing type. Keep looking upwards until we find one (or not).

The slightly tricky bit is that the compiler emits the Nullable and NullableContext attributes directly into the assembly, which means that each assembly will have its own definition. So we need to use reflection to access them - we can't cast anything to a NullableAttribute or NullableContextAttribute.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文