C# 8.0 引入了可空引用类型。以下是一个带有可空属性的简单类:
public class Foo
{
public String? Bar { get; set; }
}
有没有一种方法可以通过反射来检查类属性是否使用了可空引用类型?
C# 8.0 引入了可空引用类型。以下是一个带有可空属性的简单类:
public class Foo
{
public String? Bar { get; set; }
}
有没有一种方法可以通过反射来检查类属性是否使用了可空引用类型?
NullabilityInfoContext
API来处理这个问题。请参见此答案。
在此之前,您需要自己阅读属性。这似乎有效,至少我已经测试过的类型如此。
public static bool IsNullable(PropertyInfo property) =>
IsNullableHelper(property.PropertyType, property.DeclaringType, property.CustomAttributes);
public static bool IsNullable(FieldInfo field) =>
IsNullableHelper(field.FieldType, field.DeclaringType, field.CustomAttributes);
public static bool IsNullable(ParameterInfo parameter) =>
IsNullableHelper(parameter.ParameterType, parameter.Member, parameter.CustomAttributes);
private static bool IsNullableHelper(Type memberType, MemberInfo? declaringType, IEnumerable<CustomAttributeData> customAttributes)
{
if (memberType.IsValueType)
return Nullable.GetUnderlyingType(memberType) != null;
var nullable = 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;
}
}
for (var type = declaringType; type != null; type = type.DeclaringType)
{
var context = type.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
表示“未知”。ICommon
而不是 IBusinessRelation
。每个接口都定义了自己的 NullableContext
。我已经澄清了我的答案,并添加了一个运行时检查。 - canton7.NET 6 Preview 7增加了反射API以获取空值信息。
显然,这只对目标为.NET 6+的人有所帮助。
Getting top-level nullability information
Imagine you’re implementing a serializer. Using these new APIs the serializer can check whether a given property can be set to null:
private NullabilityInfoContext _nullabilityContext = new NullabilityInfoContext(); private void DeserializePropertyValue(PropertyInfo p, object instance, object? value) { if (value is null) { var nullabilityInfo = _nullabilityContext.Create(p); if (nullabilityInfo.WriteState is not NullabilityState.Nullable) { throw new MySerializerException($"Property '{p.GetType().Name}.{p.Name}'' cannot be set to null."); } } p.SetValue(instance, value); }
回答晚了。
这是我最终采用的方案,感谢Bill Menees提供的帮助:
static bool IsMarkedAsNullable(PropertyInfo p)
{
return new NullabilityInfoContext().Create(p).WriteState is NullabilityState.Nullable;
}
// 测试:
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
我编写了一个库来反射NRT类型 - 在内部,它查看生成的属性并为您提供简单的API:
只有string?
有点棘手。其他可空类型很容易找到。对于字符串,我使用了以下方法,您需要通过反射传递一个PropertyInfo
对象。
private bool IsNullable(PropertyInfo prop)
{
return prop.CustomAttributes.Any(x => x.AttributeType.Name == "NullableAttribute");
}
这里有@rico-suter提供的一个很棒的答案!
下面是为那些只想要一个快速的剪切和粘贴解决方案直到真正的方法可用(请参见https://github.com/dotnet/runtime/issues/29723提议)。我根据@canton7上面的帖子加上对@rico-suter代码中的想法进行简短的查看而制作了这个。与@canton7的代码不同之处仅在于抽象化属性源列表并包含一些新内容。
private static bool IsAttributedAsNonNullable(this PropertyInfo propertyInfo)
{
return IsAttributedAsNonNullable(
new dynamic?[] { propertyInfo },
new dynamic?[] { propertyInfo.DeclaringType, propertyInfo.DeclaringType?.DeclaringType, propertyInfo.DeclaringType?.GetTypeInfo() }
);
}
private static bool IsAttributedAsNonNullable(this ParameterInfo parameterInfo)
{
return IsAttributedAsNonNullable(
new dynamic?[] { parameterInfo },
new dynamic?[] { parameterInfo.Member, parameterInfo.Member.DeclaringType, parameterInfo.Member.DeclaringType?.DeclaringType, parameterInfo.Member.DeclaringType?.GetTypeInfo()
);
}
private static bool IsAttributedAsNonNullable( dynamic?[] nullableAttributeSources, dynamic?[] nullableContextAttributeSources)
{
foreach (dynamic? nullableAttributeSource in nullableAttributeSources) {
if (nullableAttributeSource == null) { continue; }
CustomAttributeData? nullableAttribute = ((IEnumerable<CustomAttributeData>)nullableAttributeSource.CustomAttributes).FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute");
if (nullableAttribute != null && nullableAttribute.ConstructorArguments.Count == 1) {
CustomAttributeTypedArgument attributeArgument = nullableAttribute.ConstructorArguments[0];
if (attributeArgument.ArgumentType == typeof(byte[])) {
var args = (ReadOnlyCollection<CustomAttributeTypedArgument>)(attributeArgument.Value ?? throw new NullReferenceException("Logic error!"));
if (args.Count > 0 && args[0].ArgumentType == typeof(byte)) {
byte value = (byte)(args[0].Value ?? throw new NullabilityLogicException());
return value == 1; // 0 = oblivious, 1 = nonnullable, 2 = nullable
}
} else if (attributeArgument.ArgumentType == typeof(byte)) {
byte value = (byte)(attributeArgument.Value ?? throw new NullReferenceException("Logic error!"));
return value == 1; // 0 = oblivious, 1 = nonnullable, 2 = nullable
} else {
throw new InvalidOperationException($"Unrecognized argument type for NullableAttribute.");
}
}
}
foreach (dynamic? nullableContextAttributeSource in nullableContextAttributeSources) {
if (nullableContextAttributeSource == null) { continue; }
CustomAttributeData? nullableContextAttribute = ((IEnumerable<CustomAttributeData>)nullableContextAttributeSource.CustomAttributes).FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableContextAttribute");
if (nullableContextAttribute != null && nullableContextAttribute.ConstructorArguments.Count == 1) {
CustomAttributeTypedArgument attributeArgument = nullableContextAttribute.ConstructorArguments[0];
if (attributeArgument.ArgumentType == typeof(byte)) {
byte value = (byte)(nullableContextAttribute.ConstructorArguments[0].Value ?? throw new NullabilityLogicException());
return value == 1;
} else {
throw new InvalidOperationException($"Unrecognized argument type for NullableContextAttribute.");
}
}
}
return false;
}
Foo
)添加[NullableContext(2), Nullable((byte) 0)]
。因此需要检查这个,但我需要深入了解如何解释这些规则! - Marc Gravellstring? X
没有任何属性,而string Y
在访问器上带有[NullableContext(2)]
和[Nullable((byte)2)]
。 - Marc GravellNullableContext
表示。如果有混合,则还要使用Nullable
。NullableContext
是一种优化,试图避免在各个地方都发出Nullable
。 - canton7