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

5

我有一个使用C#-8和可空类型的.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; }

}

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

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

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

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

然而,期望的返回值是 ["NullableInt", "NullableString", "MyNullableSubClass"]

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

我的理解是,在确定引用类型是否可空时,需要检查其是否具有 Nullable 特性。但是,对于字符串类型来说似乎并非如此。所有的字符串似乎都已经定义了可空特性。是否有一种方式可以找出 string 是否可空(即是否使用可空运算符 ? 定义)?


2
这篇文章可能是下一篇的重复:如何使用.NET反射来检查可空引用类型 - Iliar Turdushev
这比这里或链接问题下的任何答案都要复杂得多。一个叫做Namotion.Reflection 的库有一个看起来很妙的实现。 - Timo
2个回答

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

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


5
我有一篇博客文章,您可能会发现有用:https://codeblog.jonskeet.uk/2019/02/10/nullableattribute-and-c-8/ - 请注意,对于通用类型,它变得相当复杂。 - Jon Skeet
我必须在仅有两个简单的引用类型(其中一个是string)中确定可空性,并通过使用递归静态方法将其向上遍历DeclaringType层次结构,寻找NullableContextAttribute来完成。在我的情况下,有时该属性在层次结构中更高处被发现。 - liviriniu

1
你需要检查属性本身的自定义属性,而不是属性类型。
    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.CustomAttributes
                   .FirstOrDefault(a => a.AttributeType.Name == "NullableAttribute");
                isNullable = nullableAttribute == null;
            }

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

此外,如果属性可为空,则未定义此属性。如果属性不可为空,则存在该属性。

谢谢 Viktor。我认为最后一句话应该反过来说。此外,这个问题还有更多的内容。我还需要查看类上的自定义属性。我在下面发布了完整的解决方案。 - Duncan Gichimu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接