Skip to content

Instantly share code, notes, and snippets.

@Kittoes0124
Last active February 12, 2024 03:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kittoes0124/0772b1537bbf6161d402dd941595c6a8 to your computer and use it in GitHub Desktop.
Save Kittoes0124/0772b1537bbf6161d402dd941595c6a8 to your computer and use it in GitHub Desktop.
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