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

sha*_*are 11 c# reflection nullable nullable-reference-types

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

public class Foo
{
    public String? Bar { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

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

Bil*_*ees 38

.NET 6 Preview 7 添加了反射 API 来获取可空性信息。

\n

库:用于可空性信息的反射 API

\n

显然,这只能帮助那些针对 .NET 6+ 的人。

\n
\n

获取顶级可空性信息

\n

想象一下您\xe2\x80\x99正在实现一个序列化器。使用这些新的 API,序列化程序可以检查给定属性是否可以设置为 null:

\n
private NullabilityInfoContext _nullabilityContext = new NullabilityInfoContext();\n\nprivate void DeserializePropertyValue(PropertyInfo p, object instance, object? value)\n{\n    if (value is null)\n    {\n        var nullabilityInfo = _nullabilityContext.Create(p);\n        if (nullabilityInfo.WriteState is not NullabilityState.Nullable)\n        {\n            throw new MySerializerException($"Property \'{p.GetType().Name}.{p.Name}\'\' cannot be set to null.");\n        }\n    }\n\n    p.SetValue(instance, value);\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n


Ton*_*Nam 15

迟到的答复。

感谢 Bill Menees,这就是我最终使用的:

static bool IsMarkedAsNullable(PropertyInfo p)
{
    return new NullabilityInfoContext().Create(p).WriteState is NullabilityState.Nullable;
}
Run Code Online (Sandbox Code Playgroud)

// 测试:


class Foo
{
        public int Int1 { get; set; }
        public int? Int2 { get; set; } = null;


        public string Str1 { get; set; } = "";
        public string? Str2 { get; set; } = null;

        
        public List<Foo> Lst1 { get; set; } = new();
        public List<Foo>? Lst2 { get; set; } = null;


        public Dictionary<int, object> Dic1 { get; set; } = new();
        public Dictionary<int, object>? Dic2 { get; set; } = null;
}

....

var props = typeof(Foo).GetProperties();
foreach(var prop in props)
{
    Console.WriteLine($"Prop:{prop.Name} IsNullable:{IsMarkedAsNullable(prop)}");
}


// outputs:

Prop:Int1 IsNullable:False
Prop:Int2 IsNullable:True
Prop:Str1 IsNullable:False
Prop:Str2 IsNullable:True
Prop:Lst1 IsNullable:False
Prop:Lst2 IsNullable:True
Prop:Dic1 IsNullable:False
Prop:Dic2 IsNullable:True

Run Code Online (Sandbox Code Playgroud)

  • 这应该是公认的答案。简单明了。我所做的唯一更改是将 NullabilityInfoContext 创建为静态,以便可以重用。这有什么缺点吗? (2认同)

Ric*_*ter 7

我编写了一个库来反射 NRT 类型 - 在内部它查看生成的属性并为您提供一个简单的 API:

https://github.com/RicoSuter/Namotion.Reflection

  • 我正在使用这个库,但是围绕缓存存在许多不正确(且低效)的锁使用,这些锁使用在并发情况下可能会失败。因此,我“强烈”建议管理从此库公开的类型的并发。有关示例,请参阅 https://twitter.com/casperOne/status/1388962185813102595。 (2认同)

can*_*on7 6

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

您需要传递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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

  • 我将所有这些封装在一个库中,请参阅 https://github.com/RicoSuter/Namotion.Reflection (3认同)
  • 没什么大不了的。我只是想提一下。这个可空的东西让我发疯;-) (2认同)