Last active
February 12, 2024 03:08
-
-
Save Kittoes0124/0772b1537bbf6161d402dd941595c6a8 to your computer and use it in GitHub Desktop.
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
using CommunityToolkit.Diagnostics; | |
using System.ComponentModel.DataAnnotations.Schema; | |
using System.IO.Hashing; | |
using System.Numerics; | |
using System.Reflection; | |
using System.Reflection.Emit; | |
using System.Text; | |
using System.Text.Json.Nodes; | |
using System.Text.Json; | |
namespace Sandbox; | |
public static class TypeBuilderExtensions | |
{ | |
private static readonly ModuleBuilder m_moduleBuilder; | |
static TypeBuilderExtensions() { | |
var assemblyName = $"{typeof(TypeBuilderExtensions).Assembly.GetName().Name}.DynamicPocos"; | |
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( | |
access: AssemblyBuilderAccess.Run, | |
name: new AssemblyName(assemblyName: assemblyName) | |
); | |
var moduleBuilder = assemblyBuilder.DefineDynamicModule(name: $"{assemblyName}.Module"); | |
m_moduleBuilder = moduleBuilder; | |
} | |
private static MethodAttributes GetPropertyMethodAttributes( | |
bool isPublic, | |
bool isStatic | |
) { | |
var methodAttributes = (MethodAttributes.HideBySig | MethodAttributes.SpecialName); | |
if (isPublic) { | |
methodAttributes |= MethodAttributes.Public; | |
} | |
else { | |
methodAttributes |= MethodAttributes.Private; | |
} | |
if (isStatic) { | |
methodAttributes |= MethodAttributes.Static; | |
} | |
return methodAttributes; | |
} | |
private static Type GetTypeFromName(string name) { | |
var isNullableType = name.EndsWith(value: '?'); | |
if (isNullableType) { | |
name = name[..^1]; | |
} | |
var isArrayType = name.EndsWith(value: "[]"); | |
if (isArrayType) { | |
name = name[..^2]; | |
} | |
var type = name.ToLowerInvariant() switch { | |
"bigint" => typeof(BigInteger), | |
"bool" => typeof(bool), | |
"byte" => typeof(byte), | |
"char" => typeof(char), | |
"date" => typeof(DateOnly), | |
"datetimeoffset" => typeof(DateTimeOffset), | |
"decimal" => typeof(decimal), | |
"double" => typeof(double), | |
"float" => typeof(float), | |
"int" => typeof(int), | |
"long" => typeof(long), | |
"object" => typeof(object), | |
"sbyte" => typeof(sbyte), | |
"short" => typeof(short), | |
"string" => typeof(string), | |
"time" => typeof(TimeOnly), | |
"timespan" => typeof(TimeSpan), | |
"uint" => typeof(uint), | |
"ulong" => typeof(ulong), | |
"ushort" => typeof(ushort), | |
_ => ThrowHelper.ThrowNotSupportedException<Type>(message: $"\"{name}\" is not a supported type name."), | |
}; | |
if (isArrayType) { | |
type = type.MakeArrayType(); | |
} | |
else if (isNullableType && type.IsValueType) { | |
type = typeof(Nullable<>).MakeGenericType(typeArguments: type); | |
} | |
return type; | |
} | |
public static PropertyBuilder AddProperty( | |
this TypeBuilder typeBuilder, | |
bool isPublic, | |
bool isStatic, | |
string propertyName, | |
int? propertyOrdinal, | |
Type propertyType | |
) { | |
var fieldBuilder = typeBuilder.DefineField( | |
attributes: (FieldAttributes.Private | (isStatic ? FieldAttributes.Static : FieldAttributes.PrivateScope)), | |
fieldName: $"m_{propertyName}", | |
type: propertyType | |
); | |
var methodAttributes = GetPropertyMethodAttributes( | |
isPublic: isPublic, | |
isStatic: isStatic | |
); | |
var propertyBuilder = typeBuilder.DefineProperty( | |
attributes: PropertyAttributes.None, | |
callingConvention: (isStatic ? CallingConventions.Standard : CallingConventions.HasThis), | |
name: propertyName, | |
parameterTypes: null, | |
returnType: propertyType | |
); | |
if (propertyOrdinal.HasValue) { | |
var columnAttributeType = typeof(ColumnAttribute); | |
var columnAttributeConstructor = columnAttributeType.GetConstructor(types: Type.EmptyTypes); | |
var columnAttributeOrderProperty = columnAttributeType.GetProperty(name: "Order"); | |
propertyBuilder.SetCustomAttribute(customBuilder: new( | |
con: columnAttributeConstructor!, | |
constructorArgs: [], | |
namedProperties: [columnAttributeOrderProperty!,], | |
propertyValues: [propertyOrdinal.Value,] | |
)); | |
} | |
propertyBuilder.SetGetMethod(mdBuilder: fieldBuilder.AddPropertyGetMethod( | |
methodAttributes: methodAttributes, | |
propertyName: propertyName, | |
propertyType: propertyType, | |
typeBuilder: typeBuilder | |
)); | |
propertyBuilder.SetSetMethod(mdBuilder: fieldBuilder.AddPropertySetMethod( | |
methodAttributes: methodAttributes, | |
propertyName: propertyName, | |
propertyType: propertyType, | |
typeBuilder: typeBuilder | |
)); | |
return propertyBuilder; | |
} | |
public static MethodBuilder AddPropertyGetMethod( | |
this FieldBuilder fieldBuilder, | |
MethodAttributes methodAttributes, | |
string propertyName, | |
Type propertyType, | |
TypeBuilder typeBuilder | |
) { | |
var isStatic = methodAttributes.HasFlag(flag: MethodAttributes.Static); | |
var methodBuilder = typeBuilder.DefineMethod( | |
attributes: methodAttributes, | |
name: $"get_{propertyName}", | |
parameterTypes: Type.EmptyTypes, | |
returnType: propertyType | |
); | |
var ilGenerator = methodBuilder.GetILGenerator(); | |
if (isStatic) { | |
ilGenerator.Emit( | |
field: fieldBuilder, | |
opcode: OpCodes.Ldsfld | |
); | |
} | |
else { | |
ilGenerator.Emit(opcode: OpCodes.Ldarg_0); | |
ilGenerator.Emit( | |
field: fieldBuilder, | |
opcode: OpCodes.Ldfld | |
); | |
} | |
ilGenerator.Emit(opcode: OpCodes.Ret); | |
return methodBuilder; | |
} | |
public static MethodBuilder AddPropertySetMethod( | |
this FieldBuilder fieldBuilder, | |
MethodAttributes methodAttributes, | |
string propertyName, | |
Type propertyType, | |
TypeBuilder typeBuilder | |
) { | |
var isStatic = methodAttributes.HasFlag(flag: MethodAttributes.Static); | |
var methodBuilder = typeBuilder.DefineMethod( | |
attributes: methodAttributes, | |
callingConvention: (isStatic ? CallingConventions.Standard : CallingConventions.HasThis), | |
name: $"set_{propertyName}", | |
parameterTypes: [propertyType,], | |
returnType: null | |
); | |
var ilGenerator = methodBuilder.GetILGenerator(); | |
ilGenerator.Emit(opcode: OpCodes.Ldarg_0); | |
if (isStatic) { | |
ilGenerator.Emit( | |
field: fieldBuilder, | |
opcode: OpCodes.Stsfld | |
); | |
} | |
else { | |
ilGenerator.Emit(opcode: OpCodes.Ldarg_1); | |
ilGenerator.Emit( | |
field: fieldBuilder, | |
opcode: OpCodes.Stfld | |
); | |
} | |
ilGenerator.Emit(opcode: OpCodes.Ret); | |
return methodBuilder; | |
} | |
public static Type GenerateType(this IEnumerable<(string Name, int? Ordinal, Type Type)> properties) { | |
if (properties is null) { | |
ArgumentNullException.ThrowIfNull( | |
argument: properties, | |
paramName: nameof(properties) | |
); | |
} | |
var moduleBuilder = m_moduleBuilder; | |
var rowCount = 0; | |
var stringBuilder = new StringBuilder(); | |
foreach (var propertyMetadata in properties) { | |
if (256 < ++rowCount) { | |
ThrowHelper.ThrowInvalidOperationException(message: "Property limit of 256 exceeded."); | |
} | |
stringBuilder.Append(value: ((char)30)); | |
stringBuilder.AppendJoin( | |
separator: ((char)31), | |
values: [ | |
propertyMetadata.Name, | |
propertyMetadata.Type.FullName, | |
] | |
); | |
} | |
var typeHashInput = Encoding.UTF8.GetBytes(s: stringBuilder.ToString(length: (stringBuilder.Length - 1), startIndex: 1)); | |
var typeHashOutput = Convert.ToHexString(inArray: XxHash128.Hash(source: new ReadOnlySpan<byte>(array: typeHashInput))); | |
var typeName = $"__{typeHashOutput}"; | |
var type = moduleBuilder.GetType(className: typeName); | |
if (type is null) { | |
var typeBuilder = moduleBuilder.DefineType( | |
attr: ( | |
TypeAttributes.BeforeFieldInit | |
| TypeAttributes.Public | |
| TypeAttributes.Sealed | |
), | |
name: typeName | |
); | |
foreach (var propertyMetadata in properties) { | |
typeBuilder.AddProperty( | |
isPublic: true, | |
isStatic: false, | |
propertyName: propertyMetadata.Name, | |
propertyOrdinal: propertyMetadata.Ordinal, | |
propertyType: propertyMetadata.Type | |
); | |
} | |
type = typeBuilder.CreateType(); | |
} | |
return type; | |
} | |
public static Type GenerateType(this IEnumerable<PropertyMetadata> properties) => | |
properties | |
.Select(selector: propertyMetadata => ( | |
propertyMetadata.Name, | |
propertyMetadata.Ordinal, | |
propertyMetadata.Type | |
)) | |
.GenerateType(); | |
public static Type GenerateType(this JsonNode? jsonNode) { | |
if (jsonNode is null) { | |
ArgumentNullException.ThrowIfNull( | |
argument: jsonNode, | |
paramName: nameof(jsonNode) | |
); | |
} | |
if (JsonValueKind.Object == jsonNode.GetValueKind()) { | |
var typeNode = jsonNode[propertyName: "type"]; | |
if (typeNode is null) { | |
return jsonNode | |
.AsObject() | |
.Select(selector: property => { | |
int? ordinal = default; | |
if (JsonValueKind.Object == property.Value!.GetValueKind()) { | |
ordinal = property.Value![propertyName: "ordinal"]?.GetValue<int>(); | |
} | |
return new PropertyMetadata { | |
Name = property.Key, | |
Ordinal = ordinal, | |
Type = GenerateType(jsonNode: property.Value), | |
}; | |
}) | |
.GenerateType(); | |
} | |
return typeNode.GenerateType(); | |
} | |
return GetTypeFromName(name: jsonNode.GetValue<string>()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment