Last active
April 23, 2021 00:15
-
-
Save terrajobst/010590c36b4745c7c9c7415afeda936d to your computer and use it in GitHub Desktop.
This file contains hidden or 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
using System; | |
using System.Collections.Generic; | |
using System.Collections.ObjectModel; | |
using System.Reflection; | |
using System.Text; | |
namespace ConsoleApp52 | |
{ | |
internal static class Program | |
{ | |
private static void Main() | |
{ | |
var type = typeof(Test); | |
var context = new NullabilityInfoContext(); | |
foreach (var field in type.GetFields()) | |
{ | |
var info = context.Create(field); | |
Console.WriteLine($"Field {field.Name}"); | |
PrintNullabilityInformation(info); | |
} | |
foreach (var property in type.GetProperties()) | |
{ | |
var info = context.Create(property); | |
Console.WriteLine($"Property {property.Name}"); | |
PrintNullabilityInformation(info); | |
} | |
foreach (var @event in type.GetEvents()) | |
{ | |
var info = context.Create(@event); | |
Console.WriteLine($"Event {@event.Name}"); | |
PrintNullabilityInformation(info); | |
} | |
foreach (var method in type.GetMethods()) | |
{ | |
Console.WriteLine($"Method {method.Name}"); | |
Console.WriteLine(" return"); | |
PrintNullabilityInformation(context.Create(method.ReturnParameter), 2); | |
foreach (var parameter in method.GetParameters()) | |
{ | |
var info = context.Create(parameter); | |
Console.WriteLine($" {parameter.Name}"); | |
PrintNullabilityInformation(info, 2); | |
} | |
} | |
} | |
private static void PrintNullabilityInformation(NullabilityInfo info, int indent = 1) | |
{ | |
Console.Write(new string(' ', indent * 4)); | |
Console.WriteLine(info); | |
} | |
} | |
class Test | |
{ | |
#nullable disable | |
public string PropertyUnknown { get; set; } | |
#nullable restore | |
public string? PropertyNullable { get; set; } | |
public string PropertyNonNullable { get; set; } | |
#nullable disable | |
public string FieldUnknown; | |
#nullable restore | |
public string? FieldNullable; | |
public string FieldNonNullable; | |
public IEnumerable<string>? M1() => throw null!; | |
public IEnumerable<string?> M2() => throw null!; | |
public IEnumerable<string?>? M3() => throw null!; | |
public IEnumerable< | |
#nullable disable | |
string | |
#nullable restore | |
>? M4() => throw null!; | |
public IEnumerable<KeyValuePair<(string name, object? value), object?>>? M5() => throw null!; | |
public string?[] M6() => throw null!; | |
public string?[]? M7() => throw null!; | |
public string[]? M8() => throw null!; | |
} | |
} | |
namespace System.Reflection | |
{ | |
public sealed class NullabilityInfoContext | |
{ | |
private static NullableState GetNullableContext(MemberInfo? memberInfo) | |
{ | |
while (memberInfo != null) | |
{ | |
var attributes = memberInfo.GetCustomAttributesData(); | |
foreach (var attribute in attributes) | |
{ | |
if (attribute.AttributeType.Name == "NullableContextAttribute" && | |
attribute.AttributeType.Namespace == "System.Runtime.CompilerServices" && | |
attribute.ConstructorArguments.Count == 1) | |
{ | |
return TranslateByte(attribute.ConstructorArguments[0].Value); | |
} | |
} | |
memberInfo = memberInfo.DeclaringType; | |
} | |
return NullableState.Unknown; | |
} | |
public NullabilityInfo Create(ParameterInfo parameterInfo) | |
{ | |
var context = GetNullableContext(parameterInfo.Member); | |
return GetNullabilityInfo(context, parameterInfo.ParameterType, parameterInfo.GetCustomAttributesData()); | |
} | |
public NullabilityInfo Create(PropertyInfo propertyInfo) | |
{ | |
var context = GetNullableContext(propertyInfo); | |
return GetNullabilityInfo(context, propertyInfo.PropertyType, propertyInfo.GetCustomAttributesData()); | |
} | |
public NullabilityInfo Create(EventInfo eventInfo) | |
{ | |
var context = GetNullableContext(eventInfo); | |
return GetNullabilityInfo(context, eventInfo.EventHandlerType!, eventInfo.GetCustomAttributesData()); | |
} | |
public NullabilityInfo Create(FieldInfo parameterInfo) | |
{ | |
var context = GetNullableContext(parameterInfo); | |
return GetNullabilityInfo(context, parameterInfo.FieldType, parameterInfo.GetCustomAttributesData()); | |
} | |
private static NullabilityInfo GetNullabilityInfo(NullableState context, Type type, IList<CustomAttributeData> customAttributes) | |
{ | |
var offset = 0; | |
return GetNullabilityInfo(context, type, customAttributes, ref offset); | |
} | |
private static NullabilityInfo GetNullabilityInfo(NullableState context, Type type, IList<CustomAttributeData> customAttributes, ref int offset) | |
{ | |
NullableState state; | |
if (type.IsValueType) | |
{ | |
state = NullableState.Unknown; | |
} | |
else | |
{ | |
state = context; | |
foreach (var attribute in customAttributes) | |
{ | |
if (attribute.AttributeType.Name == "NullableAttribute" && | |
attribute.AttributeType.Namespace == "System.Runtime.CompilerServices" && | |
attribute.ConstructorArguments.Count == 1) | |
{ | |
var o = attribute.ConstructorArguments[0].Value; | |
if (o is byte b) | |
state = TranslateByte(b); | |
else if (o is ReadOnlyCollection<CustomAttributeTypedArgument> args && | |
offset < args.Count && | |
args[offset].Value is byte elementB) | |
state = TranslateByte(elementB); | |
else | |
state = NullableState.Unknown; | |
break; | |
} | |
} | |
} | |
// We consumed one element in the nullable array. | |
offset++; | |
NullabilityInfo? elementState = null; | |
NullabilityInfo[]? genericArgumentStates = null; | |
if (type.HasElementType) | |
{ | |
var elementType = type.GetElementType(); | |
if (elementType != null) | |
elementState = GetNullabilityInfo(context, elementType, customAttributes, ref offset); | |
} | |
else if (type.IsGenericType) | |
{ | |
var genericArguments = type.GetGenericArguments(); | |
genericArgumentStates = new NullabilityInfo[genericArguments.Length]; | |
for (int i = 0; i < genericArguments.Length; i++) | |
{ | |
genericArgumentStates[i] = GetNullabilityInfo(context, genericArguments[i], customAttributes, ref offset); | |
} | |
} | |
return new NullabilityInfo(type, state, elementState, genericArgumentStates); | |
} | |
private static NullableState TranslateByte(object? singleValue) | |
{ | |
return singleValue is byte b ? TranslateByte(b) : NullableState.Unknown; | |
} | |
private static NullableState TranslateByte(byte b) | |
{ | |
return b switch | |
{ | |
1 => NullableState.NotNull, | |
2 => NullableState.MaybeNull, | |
_ => NullableState.Unknown, | |
}; | |
} | |
} | |
public sealed class NullabilityInfo | |
{ | |
internal NullabilityInfo(Type type, NullableState state, NullabilityInfo? element, NullabilityInfo[]? genericTypeArguments) | |
{ | |
Type = type; | |
State = state; | |
Element = element; | |
GenericTypeArguments = genericTypeArguments; | |
} | |
public Type Type { get; } | |
public NullableState State { get; } | |
public NullabilityInfo? Element { get; } | |
public NullabilityInfo[]? GenericTypeArguments { get; } | |
public override string ToString() | |
{ | |
var sb = new StringBuilder(); | |
AppendType(sb, this); | |
return sb.ToString(); | |
static string GetTypeName(Type type) | |
{ | |
if (type.IsArray) | |
return GetTypeName(type.GetElementType()!); | |
if (type.IsGenericType && !type.IsGenericTypeDefinition) | |
return GetTypeName(type.GetGenericTypeDefinition()); | |
if (type.IsGenericTypeDefinition) | |
{ | |
var backTick = type.Name.IndexOf('`'); | |
if (backTick > 0) | |
return type.Name.Substring(0, backTick); | |
} | |
return type.Name; | |
} | |
static void AppendType(StringBuilder sb, NullabilityInfo info) | |
{ | |
sb.Append(GetTypeName(info.Type)); | |
if (info.State == NullableState.NotNull) | |
sb.Append('!'); | |
else if (info.State == NullableState.MaybeNull) | |
sb.Append('?'); | |
if (info.Element is not null) | |
{ | |
sb.Append('['); | |
AppendType(sb, info.Element); | |
sb.Append(']'); | |
} | |
else if (info.GenericTypeArguments is not null) | |
{ | |
sb.Append('<'); | |
var isFirst = true; | |
foreach (var arg in info.GenericTypeArguments) | |
{ | |
if (isFirst) | |
isFirst = false; | |
else | |
sb.Append(", "); | |
AppendType(sb, arg); | |
} | |
sb.Append('>'); | |
} | |
} | |
} | |
} | |
public enum NullableState | |
{ | |
Unknown, | |
NotNull, | |
MaybeNull | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment