我有一个使用C#-8的.NET Core项目,并且启用了nullable类型。
我有以下类
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属性。
我的理解是,当确定一个引用类型是否为nullable时,你需要检查它是否有Nullable属性。然而,对于字符串类型来说,情况似乎并非如此。似乎所有的字符串上都定义了nullable属性。有什么方法可以查出一个 string
是可空的(即用可空操作符定义的 ?
).
你需要检查属性本身的自定义属性,而不是属性类型。
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;
}
另外,如果属性是nullable,这个属性就没有被定义。如果属性不是nullable,这个属性就存在。
我想出了完整的解决方案。我们需要检查属性和类上的自定义属性。
...
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.netblogpostPostSharp-internals-handling-csharp-8-nullable-reference-types