Skip to content

Instantly share code, notes, and snippets.

@terrajobst
Last active April 23, 2021 00:15
Show Gist options
  • Save terrajobst/010590c36b4745c7c9c7415afeda936d to your computer and use it in GitHub Desktop.
Save terrajobst/010590c36b4745c7c9c7415afeda936d to your computer and use it in GitHub Desktop.
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