How to use .NET reflection to check for nullable reference type
C# 8.0 introduces nullable reference types. Here's a simple class with a nullable property:
public class Foo
{
public String? Bar { get; set; }
}
Is there a way to check a class property uses a nullable reference type via reflection?
In .NET 6, APIs were added to handle this, see this answer.
Prior to this, you need to read the attributes yourself. This appears to work, at least on the types I've tested it with.
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;
}
See this document for details.
The general gist is that either the property itself can have a [Nullable]
attribute on it, or if it doesn't the enclosing type might have [NullableContext]
attribute. We first look for [Nullable]
, then if we don't find it we look for [NullableContext]
on the enclosing type.
The compiler might embed the attributes into the assembly, and since we might be looking at a type from a different assembly, we need to do a reflection-only load.
[Nullable]
might be instantiated with an array, if the property is generic. In this case, the first element represents the actual property (and further elements represent generic arguments). [NullableContext]
is always instantiated with a single byte.
A value of 2
means "nullable". 1
means "not nullable", and 0
means "oblivious".
.NET 6 Preview 7 adds reflection APIs to get nullability info.
Libraries: Reflection APIs for nullability information
Obviously, this only helps folks targeting .NET 6+.
I wrote a library to do reflection of NRT types - internally it looks at the generated attributes and gives you a simple API:
https://github.com/RicoSuter/Namotion.Reflection