Parece funcionar, pelo menos nos tipos com os quais eu testei.
Você precisa passar PropertyInfo
para a propriedade em que está interessado e também para a Type
qual essa propriedade está definida ( não um tipo derivado ou pai - deve ser o tipo exato):
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;
}
Veja este documento para detalhes.
A essência geral é que a própria propriedade pode ter um [Nullable]
atributo ou, se não, o tipo de fechamento pode ter um [NullableContext]
atributo. Primeiro procuramos e [Nullable]
, se não o encontrarmos, procuramos [NullableContext]
no tipo de anexo.
O compilador pode incorporar os atributos ao assembly e, como podemos observar um tipo de um assembly diferente, precisamos fazer uma carga somente de reflexão.
[Nullable]
pode ser instanciado com uma matriz, se a propriedade for genérica. Nesse caso, o primeiro elemento representa a propriedade real (e outros elementos representam argumentos genéricos). [NullableContext]
é sempre instanciado com um único byte.
Um valor de 2
significa "anulável". 1
significa "não anulável" e 0
significa "inconsciente".
[NullableContext(2), Nullable((byte) 0)]
ao tipo (Foo
) - então é isso o que verificar, mas eu precisaria cavar mais para entender as regras de como interpretar isso!