Skip to content

Instantly share code, notes, and snippets.

@nyctef
Created June 19, 2020 13:30
Show Gist options
  • Save nyctef/b9c69ee44cebc7df9cc0eaab9c9a5076 to your computer and use it in GitHub Desktop.
Save nyctef/b9c69ee44cebc7df9cc0eaab9c9a5076 to your computer and use it in GitHub Desktop.
Check if a constructor parameter is nullable
#nullable enable
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
public static partial class Utils
{
/// <summary>Based on https://stackoverflow.com/a/58454489 with simplifications/comments added</summary>
public static bool IsNullable(ConstructorInfo constructor, ParameterInfo parameter)
{
// first we check for a nullable value type. This is relatively straightforward:
if (parameter.ParameterType.IsGenericType
&& parameter.ParameterType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return true;
}
// The rest of this method checks for nullable reference types.
// see https://github.com/dotnet/roslyn/blob/master/docs/features/nullable-metadata.md for more information
// the compiler embeds nullability information into an internal NullableAttribute value
var nullableAttribute = parameter.CustomAttributes.FirstOrDefault(
x => x.AttributeType.FullName
== "System.Runtime.CompilerServices.NullableAttribute");
if (IsNullable(nullableAttribute))
{
return true;
}
// As a size optimisation, the compiler may combine the most common nullability value into a
// NullableContextAttribute on the constructor or type instead, so we try to fall back to that.
var contextAttribute = constructor.CustomAttributes.FirstOrDefault(
x => x.AttributeType.FullName
== "System.Runtime.CompilerServices.NullableContextAttribute");
if (IsNullable(contextAttribute))
{
return true;
}
contextAttribute = constructor.DeclaringType!.CustomAttributes.FirstOrDefault(
x => x.AttributeType.FullName
== "System.Runtime.CompilerServices.NullableContextAttribute");
if (IsNullable(contextAttribute))
{
return true;
}
// Couldn't find a suitable attribute.
// We assume not-nullable as the default state, since making a parameter required
// is more likely to break in a safe way.
return false;
}
private static bool IsNullable(CustomAttributeData? attribute)
{
// We look at the attribute's constructor arguments to find the value.
// Known byte values are 0=oblivious;1=non-nullable;2=nullable
if (attribute == null || attribute.ConstructorArguments.Count != 1)
{
return false;
}
// NullableAttribute has two constructors: (byte) and (byte[])
var attributeArgument = attribute.ConstructorArguments[0];
if (attributeArgument.ArgumentType == typeof(byte[]))
{
// the (byte[]) constructor is used for generic types with a series of arguments.
// we only care about the outermost nullability value here.
var args =
attributeArgument.Value as ReadOnlyCollection<CustomAttributeTypedArgument>;
if (args != null && args.Count > 0 && args[0].ArgumentType == typeof(byte))
{
return (byte)args[0].Value! == 2;
}
}
else if (attributeArgument.ArgumentType == typeof(byte))
{
// the (byte) constructor is used in non-generic cases
return (byte)attributeArgument.Value! == 2;
}
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment