使用反射确定 C# 中引用类型的可为空性

Dun*_*imu 5 .net c# .net-core c#-8.0 nullable-reference-types

我有使用 C#-8 并启用可为 null 类型的 .NET Core 项目。

我有以下课程

public class MyClass
{
    public int? NullableInt { get; private set; }

    public string? NullableString { get; private set; }

    public string NonNullableString { get; private set; }

    public MySubClass? MyNullableSubClass { get; private set; }

}
Run Code Online (Sandbox Code Playgroud)

我需要能够循环遍历类的所有属性并确定哪些属性可为空。

所以我的代码看起来像这样

public IEnumerable<string> GetNullableProperties(Type type)
{
    var nullableProperties = new List<string>();
    foreach (var property in type.GetProperties())
    {
       var isNullable = false;
       if (property.PropertyType.IsValueType)
       {
           isNullable = Nullable.GetUnderlyingType(property.PropertyType) != null;
       } else {
           var nullableAttribute = property.PropertyType.CustomAttributes
              .FirstOrDefault(a => a.AttributeType.Name == "NullableAttribute");
           isNullable = nullableAttribute != null;
       }

       if (isNullable)
       {
           nullableProperties.Add(property.propertyType.Name)
       }
    }
    return nullableProperties;
}
Run Code Online (Sandbox Code Playgroud)

将 的类型传递MyClass给此方法会返回["NullableInt", "NullableString", "NonNullableString", "MyNullableSubClass"]。

但是,预期返回值为["NullableInt", "NullableString", "MyNullableSubClass"]。

该属性之所以NonNullableString被确定为可为空,是因为它具有 Nullable 属性。

我的理解是,在判断引用类型是否可为空时,需要检查它是否具有 Nullable 属性。然而,字符串类型似乎并非如此。似乎所有字符串都定义了 nullable 属性。有没有办法找出 a 是否string可为空(即用可为空运算符定义?)。

Dun*_*imu 5

我想出了完整的解决方案。我们需要检查属性和类上的自定义属性。


...


private const byte NonNullableContextValue = 1;
private const byte NullableContextValue = 2;

public IEnumerable<string> GetNullableProperties(Type type)
{
    foreach (var property in type.GetProperties())
    {
       var isNullable = property.PropertyType.IsValueType
           ? Nullable.GetUnderlyingType(property.PropertyType) != null;
           : IsReferenceTypePropertyNullable(property);

       if (isNullable)
       {
           nullableProperties.Add(property.propertyType.Name)
       }
    }
    return nullableProperties;
}

private function bool IsReferenceTypePropertyNullable(PropertyInfo property)
{
    var classNullableContextAttribute = property.DeclaringType.CustomerProperties
       .FirstOrDefault(c => c.AttributeType.Name == "NullableContextAttribute")

    var classNullableContext = classNullableContextAttribute
        ?.ConstructorArguments
        .First(ca => ca.ArgumentType.Name == "Byte")
        .Value;

    // EDIT: This logic is not correct for nullable generic types
    var propertyNullableContext = property.CustomAttributes
        .FirstOrDefault(c => c.AttributeType.Name == "NullableAttribute")
        ?.ConstructorArguments
        .First(ca => ca.ArgumentType.Name == "Byte")
        .Value;

    // If the property does not have the nullable attribute then it's 
    // nullability is determined by the declaring class 
    propertyNullableContext ??= classNullableContext;

    // If NullableContextAttribute on class is not set and the property
    // does not have the NullableAttribute, then the proeprty is non nullable
    if (propertyNullableContext == null)
    {
         return true;
    }

    // nullableContext == 0 means context is null oblivious (Ex. Pre C#8)
    // nullableContext == 1 means not nullable
    // nullableContext == 2 means nullable
    switch (propertyNullableContext)
    {
        case NonNullableContextValue:
            return false;
        case NullableContextValue:
            return true;
        default:
            throw new Exception("My error message");
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是有关可为空上下文值的一些信息:https://www.postsharp.net/blog/post/PostSharp-internals-handling-csharp-8-nullable-reference-types

  • 我这里也有一篇博客文章,您可能会觉得有用:https://codeblog.jonskeet.uk/2019/02/10/nullableattribute-and-c-8/ - 请注意,对于泛型类型来说,它变得非常复杂。 (5认同)