Created
September 12, 2022 10:52
-
-
Save paxbun/bb3280e0004b0bf49d24e03ce713f5f7 to your computer and use it in GitHub Desktop.
Adding nullability to dynamically created types in C# (https://paxbun.notion.site/Adding-nullability-to-dynamically-created-types-in-C-7462e019aef1438a9aa7b0c54e2fc959)
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.Reflection; | |
using System.Reflection.Emit; | |
using System.Runtime.CompilerServices; | |
// ==== Foo ==== | |
// Foo.Nullable: Nullable | |
// Foo.NonNullable: NotNull | |
PrintNullabilityState(typeof(Foo)); | |
// ==== DynamicFoo ==== | |
// DynamicFoo.Nullable: Nullable | |
// DynamicFoo.NonNullable: NotNull | |
PrintNullabilityState(GenerateNullableTypeDynamically()); | |
void PrintNullabilityState(Type type) | |
{ | |
NullabilityInfoContext context = new(); | |
Console.WriteLine("==== {0} ====", type.FullName); | |
Console.WriteLine( | |
"{0}.Nullable: {1}\n{0}.NonNullable: {2}", | |
type.FullName, | |
context.Create(type.GetProperty("Nullable")!).ReadState, | |
context.Create(type.GetProperty("NonNullable")!).ReadState | |
); | |
Console.WriteLine(); | |
} | |
// Wrapper of the following three functions. | |
Type GenerateNullableTypeDynamically() | |
{ | |
AssemblyName assemblyName = new("MyAssembly"); | |
AssemblyBuilder assemblyBuilder | |
= AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect); | |
ModuleBuilder moduleBuilder | |
= assemblyBuilder.DefineDynamicModule(assemblyName.Name!); | |
Type nullableContextAttribute | |
= CreateNullableContextAttribute(moduleBuilder); | |
Type nullableAttribute | |
= CreateNullableAttribute(moduleBuilder); | |
Type dynamicClass | |
= CreateDynamicClass(moduleBuilder, nullableContextAttribute, nullableAttribute, "DynamicFoo"); | |
return dynamicClass; | |
} | |
// Defines System.Runtime.CompilerServices.NullableContextAttribute. | |
Type CreateNullableContextAttribute(ModuleBuilder moduleBuilder) | |
{ | |
/* | |
* namespace System.Runtime.CompilerServices { | |
* internal sealed class NullableContextAttribute : Attribute { | |
* ... | |
* } | |
* } | |
*/ | |
TypeBuilder nullableContextAttributeTypeBuilder | |
= moduleBuilder.DefineType( | |
"System.Runtime.CompilerServices.NullableContextAttribute", | |
TypeAttributes.Sealed, | |
typeof(Attribute)); | |
/* | |
* namespace System.Runtime.CompilerServices { | |
* [System.Runtime.CompilerServices.CompilerGenerated] | |
* [System.AttributeUsageAttribute( | |
* System.AttributeTargets.Class | |
* | System.AttributeTargets.Struct | |
* | System.AttributeTargets.Method | |
* | System.AttributeTargets.Interface | |
* | System.AttributeTargets.Delegate | |
* )] | |
* internal sealed class NullableContextAttribute : Attribute { | |
* ... | |
* } | |
* } | |
*/ | |
nullableContextAttributeTypeBuilder.SetCustomAttribute( | |
new CustomAttributeBuilder( | |
typeof(CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes)!, | |
Array.Empty<object?>())); | |
nullableContextAttributeTypeBuilder.SetCustomAttribute( | |
new CustomAttributeBuilder( | |
typeof(AttributeUsageAttribute).GetConstructor(new[] { typeof(AttributeTargets) })!, | |
new object[] | |
{ | |
AttributeTargets.Class | |
| AttributeTargets.Struct | |
| AttributeTargets.Method | |
| AttributeTargets.Interface | |
| AttributeTargets.Delegate | |
})); | |
/* | |
* namespace System.Runtime.CompilerServices { | |
* ... | |
* internal sealed class NullableContextAttribute : Attribute { | |
* public readonly byte Flag; | |
* ... | |
* } | |
* } | |
*/ | |
FieldBuilder fieldBuilder | |
= nullableContextAttributeTypeBuilder.DefineField( | |
"Flag", | |
typeof(byte), | |
FieldAttributes.Public | FieldAttributes.InitOnly); | |
/* | |
* namespace System.Runtime.CompilerServices { | |
* ... | |
* internal sealed class NullableContextAttribute : Attribute { | |
* public readonly byte Flag; | |
* public NullableContextAttribute(byte b): base() { | |
* Flag = b; | |
* } | |
* } | |
* } | |
*/ | |
ConstructorInfo attributeConstructorWithoutParams | |
= typeof(Attribute).GetConstructor( | |
BindingFlags.NonPublic | |
| BindingFlags.Instance, | |
Type.EmptyTypes)!; | |
ConstructorBuilder constructorBuilder | |
= nullableContextAttributeTypeBuilder.DefineConstructor( | |
MethodAttributes.Public, | |
CallingConventions.Standard, | |
new[] { typeof(byte) }); | |
// public NullableContextAttribute(byte b) | |
ILGenerator ilGenerator = constructorBuilder.GetILGenerator(); | |
// : base() { | |
ilGenerator.Emit(OpCodes.Ldarg_0); | |
ilGenerator.Emit(OpCodes.Call, attributeConstructorWithoutParams); | |
// Flag = b; | |
ilGenerator.Emit(OpCodes.Ldarg_0); | |
ilGenerator.Emit(OpCodes.Ldarg_1); | |
ilGenerator.Emit(OpCodes.Stfld, fieldBuilder); | |
// return; | |
ilGenerator.Emit(OpCodes.Ret); | |
// } | |
return nullableContextAttributeTypeBuilder.CreateType()!; | |
} | |
// Defines System.Runtime.CompilerServices.NullableAttribute. | |
Type CreateNullableAttribute(ModuleBuilder moduleBuilder) | |
{ | |
TypeBuilder nullableAttributeTypeBuilder | |
= moduleBuilder.DefineType( | |
"System.Runtime.CompilerServices.NullableAttribute", | |
TypeAttributes.Sealed, | |
typeof(Attribute)); | |
nullableAttributeTypeBuilder.SetCustomAttribute( | |
new CustomAttributeBuilder( | |
typeof(CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes)!, | |
Array.Empty<object?>())); | |
nullableAttributeTypeBuilder.SetCustomAttribute( | |
new CustomAttributeBuilder( | |
typeof(AttributeUsageAttribute).GetConstructor(new[] { typeof(AttributeTargets) })!, | |
new object[] | |
{ | |
AttributeTargets.Class| AttributeTargets.Property | AttributeTargets.Field | | |
AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | | |
AttributeTargets.GenericParameter | |
})); | |
FieldBuilder fieldBuilder | |
= nullableAttributeTypeBuilder.DefineField( | |
"NullableFlags", | |
typeof(byte[]), | |
FieldAttributes.Public | FieldAttributes.InitOnly); | |
ConstructorBuilder constructorBuilder1 | |
= nullableAttributeTypeBuilder.DefineConstructor( | |
MethodAttributes.Public, | |
CallingConventions.Standard, | |
new[] { typeof(byte) }); | |
/* | |
* public NullableAttribute(byte b) | |
* { | |
* byte[] array = new byte[1]; | |
* array[0] = b; | |
* this.NullableFlags = array; | |
* } | |
*/ | |
ConstructorInfo attributeConstructorWithoutParams | |
= typeof(Attribute).GetConstructor( | |
BindingFlags.NonPublic | BindingFlags.Instance, Type.EmptyTypes)!; | |
ILGenerator ilGenerator1 = constructorBuilder1.GetILGenerator(); | |
ilGenerator1.Emit(OpCodes.Ldarg_0); | |
ilGenerator1.Emit(OpCodes.Call, attributeConstructorWithoutParams); | |
ilGenerator1.Emit(OpCodes.Ldarg_0); | |
ilGenerator1.Emit(OpCodes.Ldc_I4_1); | |
ilGenerator1.Emit(OpCodes.Newarr, typeof(byte)); | |
ilGenerator1.Emit(OpCodes.Dup); | |
ilGenerator1.Emit(OpCodes.Ldc_I4_0); | |
ilGenerator1.Emit(OpCodes.Ldarg_1); | |
ilGenerator1.Emit(OpCodes.Stelem_I1); | |
ilGenerator1.Emit(OpCodes.Stfld, fieldBuilder); | |
ilGenerator1.Emit(OpCodes.Ret); | |
ConstructorBuilder constructorBuilder2 = nullableAttributeTypeBuilder.DefineConstructor( | |
MethodAttributes.Public, | |
CallingConventions.Standard, | |
new[] { typeof(byte[]) } | |
); | |
/* | |
* public NullableAttribute(byte[] array) | |
* { | |
* this.NullableFlags = array; | |
* } | |
*/ | |
ILGenerator ilGenerator2 = constructorBuilder2.GetILGenerator(); | |
ilGenerator2.Emit(OpCodes.Ldarg_0); | |
ilGenerator2.Emit(OpCodes.Call, attributeConstructorWithoutParams); | |
ilGenerator2.Emit(OpCodes.Ldarg_0); | |
ilGenerator2.Emit(OpCodes.Ldarg_1); | |
ilGenerator2.Emit(OpCodes.Stfld, fieldBuilder); | |
ilGenerator2.Emit(OpCodes.Ret); | |
return nullableAttributeTypeBuilder.CreateType()!; | |
} | |
Type CreateDynamicClass( | |
ModuleBuilder moduleBuilder, | |
Type nullableContextAttribute, | |
Type nullableAttribute, | |
string className | |
) | |
{ | |
/* | |
* abstract class <className> { ... } | |
*/ | |
TypeBuilder typeBuilder | |
= moduleBuilder.DefineType(className, TypeAttributes.Abstract); | |
typeBuilder.SetCustomAttribute( | |
new CustomAttributeBuilder( | |
nullableContextAttribute.GetConstructor(new[] { typeof(byte) })!, | |
new object[] { (byte)1 } | |
) | |
); | |
/* | |
* abstract class <className> { | |
* ... | |
* public abstract string? Nullable { get; } | |
* ... | |
* } | |
*/ | |
PropertyBuilder nullablePropertyBuilder | |
= typeBuilder.DefineProperty( | |
"Nullable", PropertyAttributes.None, typeof(string), null); | |
nullablePropertyBuilder.SetCustomAttribute( | |
new CustomAttributeBuilder( | |
nullableAttribute.GetConstructor(new[] { typeof(byte) })!, | |
new object[] { (byte)2 })); | |
MethodBuilder nullablePropertyGetterBuilder | |
= typeBuilder.DefineMethod( | |
"get_Nullable", | |
MethodAttributes.Public | |
| MethodAttributes.Abstract | |
| MethodAttributes.Virtual, | |
typeof(string), | |
null); | |
nullablePropertyGetterBuilder.SetCustomAttribute( | |
new CustomAttributeBuilder( | |
nullableContextAttribute.GetConstructor(new[] { typeof(byte) })!, | |
new object[] { (byte)2 })); | |
nullablePropertyBuilder.SetGetMethod(nullablePropertyGetterBuilder); | |
/* | |
* abstract class <className> { | |
* ... | |
* public abstract string NonNullable { get; } | |
* ... | |
* } | |
*/ | |
PropertyBuilder nonNullablePropertyBuilder | |
= typeBuilder.DefineProperty( | |
"NonNullable", PropertyAttributes.None, typeof(string), null); | |
MethodBuilder nonNullablePropertyGetterBuilder | |
= typeBuilder.DefineMethod( | |
"get_NonNullable", | |
MethodAttributes.Public | |
| MethodAttributes.Abstract | |
| MethodAttributes.Virtual, | |
typeof(string), | |
null); | |
nonNullablePropertyBuilder.SetGetMethod(nonNullablePropertyGetterBuilder); | |
return typeBuilder.CreateType()!; | |
} | |
class Foo | |
{ | |
public string? Nullable { get; set; } = null!; | |
public string NonNullable { get; set; } = null!; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment