Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 793
shadeglare Avatar asked Oct 18 '19 15:10

shadeglare


People also ask

How do you make a nullable reference type in C#?

There are two ways to control the nullable context. At the project level, you can add the <Nullable>enable</Nullable> project setting. In a single C# source file, you can add the #nullable enable pragma to enable the nullable context.

Are reference types null by default?

A reference isn't supposed to be null. The default state of a nonnullable reference variable is not-null. The compiler enforces rules that ensure it's safe to dereference these variables without first checking that it isn't null: The variable must be initialized to a non-null value.

Can we create nullable type based on reference type?

Nullable types represent value-type variables that can be assigned the value of null. You cannot create a nullable type based on a reference type.

Is string in C# nullable?

In C# 8.0, strings are known as a nullable “string!”, and so the AllowNull annotation allows setting it to null, even though the string that we return isn't null (for example, we do a comparison check and set it to a default value if null.)


1 Answers

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".

like image 98
canton7 Avatar answered Sep 26 '22 07:09

canton7