How to Use .Net Reflection to Check for Nullable Reference Type

How to use .NET reflection to check for nullable reference type

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

Determine Nullability Of A Reference Type in C# Using Reflection

I figured out the complete solution. We need to check the custom attributes on the property and on the class.


...

private const byte NonNullableContextValue = 1;
private const byte NullableContextValue = 2;

public IEnumerable<string> GetNullableProperties(Type type)
{
foreach (var property in type.GetProperties())
{
var isNullable = property.PropertyType.IsValueType
? Nullable.GetUnderlyingType(property.PropertyType) != null;
: IsReferenceTypePropertyNullable(property);

if (isNullable)
{
nullableProperties.Add(property.propertyType.Name)
}
}
return nullableProperties;
}

private function bool IsReferenceTypePropertyNullable(PropertyInfo property)
{
var classNullableContextAttribute = property.DeclaringType.CustomerProperties
.FirstOrDefault(c => c.AttributeType.Name == "NullableContextAttribute")

var classNullableContext = classNullableContextAttribute
?.ConstructorArguments
.First(ca => ca.ArgumentType.Name == "Byte")
.Value;

// EDIT: This logic is not correct for nullable generic types
var propertyNullableContext = property.CustomAttributes
.FirstOrDefault(c => c.AttributeType.Name == "NullableAttribute")
?.ConstructorArguments
.First(ca => ca.ArgumentType.Name == "Byte")
.Value;

// If the property does not have the nullable attribute then it's
// nullability is determined by the declaring class
propertyNullableContext ??= classNullableContext;

// If NullableContextAttribute on class is not set and the property
// does not have the NullableAttribute, then the proeprty is non nullable
if (propertyNullableContext == null)
{
return true;
}

// nullableContext == 0 means context is null oblivious (Ex. Pre C#8)
// nullableContext == 1 means not nullable
// nullableContext == 2 means nullable
switch (propertyNullableContext)
{
case NonNullableContextValue:
return false;
case NullableContextValue:
return true;
default:
throw new Exception("My error message");
}
}

Here's is some information about the nullable context values: https://www.postsharp.net/blog/post/PostSharp-internals-handling-csharp-8-nullable-reference-types

C# detect nullable reference type from generic argument of method's return type using reflection

Use MethodInfo.ReturnParameter for NullabilityInfoContext:

Type type = typeof(IMyInterface);
var methodInfo = type.GetMethod(nameof(IMyInterface.GetMyModel));

NullabilityInfoContext context = new();
var nullabilityInfo = context.Create(methodInfo.ReturnParameter); // return type
var genericTypeArguments = nullabilityInfo.GenericTypeArguments;
var myModelNullabilityInfo = genericTypeArguments.First(); // nullability info for generic argument of Task
Console.WriteLine(myModelNullabilityInfo.ReadState); // Nullable
Console.WriteLine(myModelNullabilityInfo.WriteState); // Nullable

To analyze nullable attributes methodInfo.ReturnTypeCustomAttributes.GetCustomAttributes(true) can be used.

Can you tell by Reflection which string is nullable and which is not?

No. The IL for both constructs is the same (SharpLab link):

IL_0000: nop
IL_0001: ldtoken class [System.Collections]System.Collections.Generic.List`1<string>
IL_0006: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
IL_000b: stloc.0
IL_000c: ret

where local variable 0 is class [System.Runtime]System.Type. So when compiled there is no difference between the two expressions.

How to identify a nullable reference type for generic type?

In C# 8 with nullable enabled, is there a way to identify a nullable
reference type
for generic type?

In C# 8 there is NO way to check if a type parameter passed to a generic method is a nullable reference type or not.

The problem is that any nullable reference type T? is represented by the same type T (but with a compiler-generated attribute annotating it), as opposed to nullable value type T? that is represented by the actual .NET type Nullable<T>.

When compiler generates code that invokes a generic method F<T>, where T can be either nullable reference type or not, an information if T is nullable refence type is lost. Lets consider the next sample method:

public void F<T>(T value) { }

For the next invocations

F<string>("123");
F<string?>("456");

compiler will generate the next IL code (I simplified it a bit):

call    F<string>("123")
call F<string>("456")

You can see that to the second method a type parameter string is passed instead of string? because the representation of the nullable reference type string? during the execution is the same type string.

Therefore during execution it is impossible to define if a type parameter passed to a generic method is a nullable reference type or not.


I think that for your case an optimal solution would be to pass a bool value that will indicate if a reference type is nullable or not. Here is a sample, how it can be implemented:

public static Result<T> Create<T>(T value, bool isNullable = false)
{
Type t = typeof(T);

// If type "T" is a value type then we can check if it is nullable or not.
if (t.IsValueType)
{
if (Nullable.GetUnderlyingType(t) == null && value == null)
throw new ArgumentNullException(nameof(value));
}
// If type "T" is a reference type then we cannot check if it is nullable or not.
// In this case we rely on the value of the argument "isNullable".
else
{
if (!isNullable && value == null)
throw new ArgumentNullException(nameof(value));
}

...
}

Detecting a Nullable Type via reflection

Well firstly, Nullable<T> is a struct, so there isn't an object as such. You can't call GetType(), as that will box the value (at which point you either get null and thus an exception, or a boxed non-nullable value and therefore not the type you want).

(Boxing is what's messing up your assertion here - I would assume that IsType accepts object.)

You can use type inference though to get the type of the variable as a type parameter:

public bool IsNullable<T>(T value)
{
return Nullable.GetUnderlyingType(typeof(T)) != null;
}

That's not a huge amount of use when you know the exact type at compile-time as in your example, but it's useful for generics. (There are alternative ways of implementing it, of course.)

What's your real life situation? I assume it's not an assertion like this, given that you know the answer to this one at compile time.

Using c# Nullable Reference Type annotations from different assembly

Turns out this is a bug, in roslyn's support for editorconfig.

My editorconfig had this:

dotnet_diagnostic.CA1062.severity = warning

So actually it's not a c# NRT problem.

The workaround is:

using System;
using System.Diagnostics.CodeAnalysis;

[AttributeUsage(AttributeTargets.Parameter)]
internal sealed class ValidatedNotNullAttribute : Attribute { }

namespace Common {
public static class Guard {

public static void IsNotNull([ValidatedNotNull][NotNull] object? arg, string? argName) =>
_ = arg ?? throw new ArgumentNullException(argName);

}
}

And in .editorconfig:

dotnet_diagnostic.CA1062.severity = warning
dotnet_code_quality.CA1062.null_check_validation_methods = Guard.IsNotNull

I assume once the bug is resolved, I can remove all that and it would "just work" due to NRT.



Related Topics



Leave a reply



Submit