Похоже, это работает, по крайней мере, для типов, с которыми я тестировал
Вам нужно передать PropertyInfo
интересующее вас свойство, а также то, для Type
чего это свойство определено ( не производный или родительский тип - это должен быть точный тип):
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;
}
Смотрите этот документ для деталей.
Общая суть заключается в том, что либо у самого свойства может быть [Nullable]
атрибут, либо, если оно отсутствует, у включающего типа может быть [NullableContext]
атрибут. Сначала мы ищем [Nullable]
, затем, если мы не найдем его, мы ищем [NullableContext]
в типе включения.
Компилятор может встраивать атрибуты в сборку, и, поскольку мы можем рассматривать тип из другой сборки, нам нужно выполнить загрузку только для отражения.
[Nullable]
может быть создан с помощью массива, если свойство является общим. В этом случае первый элемент представляет фактическое свойство (а остальные элементы представляют общие аргументы). [NullableContext]
всегда создается одним байтом.
Значение 2
означает «обнуляемый». 1
означает «не обнуляемый» и 0
означает «забывчивый».
[NullableContext(2), Nullable((byte) 0)]
к типу (Foo
) - вот что нужно проверять, но мне нужно больше копать, чтобы понять правила, как это интерпретировать!