Esto parece funcionar, al menos en los tipos con los que lo he probado.
Debe pasar la PropertyInfo
propiedad que le interesa y también la Type
propiedad en la que se define esa propiedad ( no un tipo derivado o primario; debe ser el tipo exacto):
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;
}
Vea este documento para más detalles.
La esencia general es que, o bien la propiedad en sí misma puede tener un [Nullable]
atributo, o si no lo tiene, el tipo que lo encierra podría tener un [NullableContext]
atributo. Primero buscamos [Nullable]
, luego, si no lo encontramos, buscamos [NullableContext]
en el tipo adjunto.
El compilador podría incrustar los atributos en el ensamblaje, y dado que podríamos estar viendo un tipo de un ensamblaje diferente, necesitamos hacer una carga de solo reflexión.
[Nullable]
podría instanciarse con una matriz, si la propiedad es genérica. En este caso, el primer elemento representa la propiedad real (y otros elementos representan argumentos genéricos). [NullableContext]
siempre se instancia con un solo byte.
Un valor de 2
significa "anulable". 1
significa "no anulable" y 0
significa "ajeno".
[NullableContext(2), Nullable((byte) 0)]
al tipo (Foo
), así que eso es lo que debe verificar, ¡pero necesitaría cavar más para comprender las reglas de cómo interpretar eso!