如何使用.NET反射检查可为空的引用类型


15

C#8.0引入了可为空的引用类型。这是一个具有可空属性的简单类:

public class Foo
{
    public String? Bar { get; set; }
}

有没有一种方法可以通过反射检查类属性是否使用可为空的引用类型?


编译并查看IL,它看起来像添加[NullableContext(2), Nullable((byte) 0)]到了类型Foo)中-这就是要检查的内容,但是我需要进一步挖掘才能理解如何解释该规则!
马克·格雷夫

4
是的,但这并不简单。幸运的是,它已被 记录在案
Jeroen Mostert

啊,我明白了;因此string? X变得没有属性,并string Y获得[Nullable((byte)2)][NullableContext(2)]在存取
马克·Gravell

1
如果类型包含可空值(或非可空值),则全部由表示NullableContext。如果有混合,也要Nullable使用。NullableContext是一种优化的尝试,可以避免不得不Nullable到处发射。
canton7

Answers:


11

这似乎起作用,至少在我测试过的类型上起作用。

您需要传递PropertyInfo您感兴趣的属性的,以及Type该属性所定义的属性(不是派生或父类型-必须是确切的类型):

public static bool IsNullable(Type enclosingType, PropertyInfo property)
{
    if (!enclosingType.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly).Contains(property))
        throw new ArgumentException("enclosingType must be the type which defines property");

    var nullable = property.CustomAttributes
        .FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute");
    if (nullable != null && nullable.ConstructorArguments.Count == 1)
    {
        var attributeArgument = nullable.ConstructorArguments[0];
        if (attributeArgument.ArgumentType == typeof(byte[]))
        {
            var args = (ReadOnlyCollection<CustomAttributeTypedArgument>)attributeArgument.Value;
            if (args.Count > 0 && args[0].ArgumentType == typeof(byte))
            {
                return (byte)args[0].Value == 2;
            }
        }
        else if (attributeArgument.ArgumentType == typeof(byte))
        {
            return (byte)attributeArgument.Value == 2;
        }
    }

    var context = enclosingType.CustomAttributes
        .FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableContextAttribute");
    if (context != null &&
        context.ConstructorArguments.Count == 1 &&
        context.ConstructorArguments[0].ArgumentType == typeof(byte))
    {
        return (byte)context.ConstructorArguments[0].Value == 2;
    }

    // Couldn't find a suitable attribute
    return false;
}

有关详细信息,请参见此文档

一般要点是,属性本身可以具有[Nullable]属性,或者如果属性本身不具有属性,则封闭类型可能具有[NullableContext]属性。我们首先寻找[Nullable],然后如果找不到,我们将[NullableContext]在封闭类型上寻找。

编译器可能会将属性嵌入到程序集中,并且由于我们可能正在查看来自不同程序集的类型,因此我们需要进行仅反射加载。

[Nullable]如果属性是通用的,则可以使用数组实例化。在这种情况下,第一个元素代表实际属性(其他元素代表通用参数)。[NullableContext]总是用单个字节实例化。

值的2意思是“可为空”。1表示“不可为空”,0表示“忽略”。


这真的很棘手。我刚刚发现此代码未涵盖的用例。公共接口IBusinessRelation : ICommon {}/ public interface ICommon { string? Name {get;set;} }。如果我IBusinessRelation用Property 调用方法,Name我将得到假。
gsharp

@gsharp啊,我还没有尝试过使用接口或任何继承方法。我猜这是一个相对容易的修复(请看一下基本接口的上下文属性):我稍后会尝试修复
-canton7

1
没关系 我只想提一提。这些可为空的东西让我发疯了;-)
gsharp

1
@gsharp查看它,您需要传递定义属性的接口类型,即ICommon,而不是IBusinessRelation。每个接口定义自己的NullableContext。我已经弄清楚了答案,并为此添加了运行时检查。
canton7
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.