Created
September 19, 2021 07:00
-
-
Save leandromoh/d932eff216fc7a3ebf2133a02fa1efd5 to your computer and use it in GitHub Desktop.
Spread object in C#
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 System; | |
using System.Buffers; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using System.Reflection.Emit; | |
using System.Text.Json; | |
using static MoreLinq.Extensions.DistinctByExtension; | |
// adapted from https://gist.github.com/AlbertoMonteiro/f6aa8c92a7d93c9b32b4c2ab19a67def | |
var cliente = new Brazilian { Nome = "Bob", Id = Guid.Parse("c301f874-007f-4b40-a191-56216e6ba98c") }; | |
var cliente2 = new American { Nome = "Leandro", Id = "A7D8" }; | |
var endereco = new Endereco { Logradouro = "Rua Mole", Numero = 123 }; | |
var opa = cliente.Spread(cliente2, endereco, new { UsaGitHub = true }); | |
// { "Nome":"Leandro","Logradouro":"Rua Mole","Numero":123,"UsaGitHub":true} | |
Console.WriteLine(JsonSerializer.Serialize(opa)); | |
// { "Id":"c301f874-007f-4b40-a191-56216e6ba98c"} | |
Console.WriteLine(JsonSerializer.Serialize(opa as IBR)); | |
// { "Id":"A7D8"} | |
Console.WriteLine(JsonSerializer.Serialize(opa as IUSA)); | |
public interface IUSA | |
{ | |
public string Id { get; set; } | |
} | |
public class American : IUSA | |
{ | |
public string Id { get; set; } | |
public string Nome { get; set; } | |
} | |
public interface IBR | |
{ | |
public Guid Id { get; set; } | |
} | |
public class Brazilian : IBR | |
{ | |
public Guid Id { get; set; } | |
public string Nome { get; set; } | |
} | |
class Endereco | |
{ | |
public string Logradouro { get; set; } | |
public int Numero { get; set; } | |
} | |
public static class Extensions | |
{ | |
static Extensions() | |
{ | |
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("AssemblyX"), AssemblyBuilderAccess.RunAndCollect); | |
ModuleBuilder = assemblyBuilder.DefineDynamicModule("MainModule"); | |
} | |
public static dynamic Spread(this object obj, params object[] anotherObject) | |
{ | |
var allobjs = anotherObject.Prepend(obj); | |
var _base = allobjs.SelectMany(x => x.GetType().GetInterfaces()).ToArray(); | |
var interfaces = allobjs.SelectMany(o => o.GetType().GetInterfaces(), (o, i) => (o, i)); | |
var propsInter = interfaces.SelectMany(x => x.i.GetProperties(), (x, p) => (x.o, p)).Reverse().DistinctBy(x => x.p).ToArray(); | |
var propertiesNotInterface = allobjs | |
.SelectMany(o => o.GetType().GetProperties(), (o, p) => (o: o, p: p)) | |
.Where(x => | |
{ | |
var result = _base.Any(i => i.IsAssignableFrom(x.p.DeclaringType) && i.GetProperty(x.p.Name) != null); | |
return result is false; | |
}) | |
.ToArray(); | |
var all = propsInter | |
.Concat(propertiesNotInterface).Select(x => x.p); | |
var objType = CreateClass(_base, all); | |
var finalObj = Activator.CreateInstance(objType); | |
var none = propertiesNotInterface | |
.ToDictionary(t => (t.p.DeclaringType, t.p), t => t.p.GetValue(t.o)); | |
var fromInte = propsInter | |
.ToDictionary(t => (t.p.DeclaringType, t.p.GetSetMethod()), t => t.p.GetValue(t.o)); | |
foreach (var prop in none) | |
{ | |
objType.GetProperty(prop.Key.p.Name).SetValue(finalObj, prop.Value); | |
} | |
foreach (var inter in _base) | |
{ | |
InterfaceMapping map = objType.GetInterfaceMap(inter); | |
for (int i = 0; i < map.InterfaceMethods.Length; i++) | |
{ | |
MethodInfo ifaceMethod = map.InterfaceMethods[i]; | |
MethodInfo targetMethod = map.TargetMethods[i]; | |
if (fromInte.TryGetValue((inter, ifaceMethod), out var value)) | |
{ | |
targetMethod.Invoke(finalObj, new[] { value }); | |
} | |
} | |
} | |
return finalObj; | |
} | |
const MethodAttributes METHOD_ATTRIBUTES = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; | |
private static ModuleBuilder ModuleBuilder; | |
internal static Type CreateClass(Type[] _base, IEnumerable<PropertyInfo> parameters) | |
{ | |
var typeBuilder = ModuleBuilder.DefineType(Guid.NewGuid().ToString(), TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout, null, interfaces: _base); | |
typeBuilder.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName); | |
foreach (var parameter in parameters) | |
CreateProperty(typeBuilder, parameter); | |
var type = typeBuilder.CreateTypeInfo().AsType(); | |
return type; | |
} | |
private static PropertyBuilder CreateProperty(TypeBuilder typeBuilder, PropertyInfo prop) | |
{ | |
var fieldBuilder = typeBuilder.DefineField($"<{prop.DeclaringType.Name}>" + prop.Name, prop.PropertyType, FieldAttributes.Private); | |
var propBuilder = typeBuilder.DefineProperty(prop.Name, PropertyAttributes.HasDefault, prop.PropertyType, null); | |
propBuilder.SetGetMethod(DefineGet(typeBuilder, fieldBuilder, propBuilder, prop.GetGetMethod())); | |
propBuilder.SetSetMethod(DefineSet(typeBuilder, fieldBuilder, propBuilder, prop.GetSetMethod())); | |
return propBuilder; | |
} | |
private static MethodBuilder DefineSet(TypeBuilder typeBuilder, FieldBuilder fieldBuilder, PropertyBuilder propBuilder, MethodInfo m) | |
=> DefineMethod(typeBuilder, $"set_{propBuilder.Name}", null, new[] { propBuilder.PropertyType }, m, il => | |
{ | |
il.Emit(OpCodes.Ldarg_0); | |
il.Emit(OpCodes.Ldarg_1); | |
il.Emit(OpCodes.Stfld, fieldBuilder); | |
il.Emit(OpCodes.Ret); | |
}); | |
private static MethodBuilder DefineGet(TypeBuilder typeBuilder, FieldBuilder fieldBuilder, PropertyBuilder propBuilder, MethodInfo m) | |
=> DefineMethod(typeBuilder, $"get_{propBuilder.Name}", propBuilder.PropertyType, Type.EmptyTypes, m, il => | |
{ | |
il.Emit(OpCodes.Ldarg_0); | |
il.Emit(OpCodes.Ldfld, fieldBuilder); | |
il.Emit(OpCodes.Ret); | |
}); | |
private static MethodBuilder DefineMethod(TypeBuilder typeBuilder, string methodName, Type propertyType, Type[] parameterTypes, MethodInfo m, Action<ILGenerator> bodyWriter) | |
{ | |
var m2 = m?.ReflectedType?.IsInterface is true | |
? m.ReflectedType.GetMethod(m.Name) | |
: null; | |
var attr = m2 is null | |
? METHOD_ATTRIBUTES | |
: MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.NewSlot | MethodAttributes.Virtual; | |
var methodBuilder = typeBuilder.DefineMethod(methodName, attr, propertyType, parameterTypes); | |
bodyWriter(methodBuilder.GetILGenerator()); | |
if (m2 != null) | |
{ | |
typeBuilder.DefineMethodOverride(methodBuilder, m2); | |
} | |
return methodBuilder; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment