Created
June 19, 2020 13:30
-
-
Save nyctef/b9c69ee44cebc7df9cc0eaab9c9a5076 to your computer and use it in GitHub Desktop.
Check if a constructor parameter is nullable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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