我有一些简单的类,一些简单的属性(简单的{get; set;}声明).所有的属性都是可空的(或等效的,引用类型).
例如:
class POCO { int? Field1 { get; set; } string Field2 { get; set; } ... etc ... }
我有一个场景,我正在构建这些POCO零碎,最后我想要得到所有非空字段中的一个.
一些说明性代码:
POCO o1 = LoadFields1To3(); POCO o2 = LoadFields4To5(); POCO o3 = LoadFields6To9(); ... etc ...
我们在这种情况下,因为某些字段是从sql(有时是不同的查询)加载的,而有些则是从内存数据结构中加载的.我在这里重新使用POCO类型,以避免一堆其他无意义的类(一种静态类型对于Dapper非常有用,而且一般而言).
我正在寻找的是将这些对象的属性合并成一个具有非空属性的属性的好方法.
就像是:
POCO final = o1.UnionProperties(o2).UnionProperties(o3) // and so on
我可以保证在多个对象上没有字段是非空的.虽然我假设一个解决方案将采用最左边的非空字段,但实际上并不是必需的.
我知道我可以写一些反思代码来做到这一点,但有点讨厌和缓慢.
这确实需要一般适用,因为虽然我从不打算合并不同类型的对象,但是这种方法将适用于大量的类型.
我想知道如果没有一些聪明的方式,也许滥用动态?
解决方法
我收集(好的,我问你),这里的关键目标是:
表现(反思似乎太慢)
>低维护(想要避免非常手动的复制方法或复杂的属性)
以下使用元编程在运行时可以即时运行,将其自身编译为类型化的代理(Action< POCO,POCO>),以便高效重用:
using System; using System.Collections.Generic; using System.Reflection.Emit; public class SamplePoco { public int? Field1 { get; set; } public string Field2 { get; set; } // lots and lots more properties here } static class Program { static void Main() { var obj1 = new SamplePoco { Field1 = 123 }; var obj2 = new SamplePoco { Field2 = "abc" }; var merged = Merger.Merge(obj1,obj2); Console.WriteLine(merged.Field1); Console.WriteLine(merged.Field2); } } static class Merger { public static T Merge<T>(params T[] sources) where T : class,new() { var merge = MergerImpl<T>.merge; var obj = new T(); for (int i = 0; i < sources.Length; i++) merge(sources[i],obj); return obj; } static class MergerImpl<T> where T : class,new() { internal static readonly Action<T,T> merge; static MergerImpl() { var method = new DynamicMethod("Merge",null,new[] { typeof(T),typeof(T) },typeof(T)); var il = method.GetILGenerator(); Dictionary<Type,LocalBuilder> locals = new Dictionary<Type,LocalBuilder>(); foreach (var prop in typeof(T).GetProperties()) { var propType = prop.PropertyType; if (propType.IsValueType && Nullable.GetUnderlyingType(propType) == null) { continue; // int,instead of int? etc - skip } il.Emit(OpCodes.Ldarg_1); // [target] il.Emit(OpCodes.Ldarg_0); // [target][source] il.EmitCall(OpCodes.Callvirt,prop.GetGetMethod(),null); // [target][value] il.Emit(OpCodes.Dup); // [target][value][value] Label nonNull = il.DefineLabel(),end = il.DefineLabel(); if (propType.IsValueType) { // int? etc - Nullable<T> - hit .Value LocalBuilder local; if (!locals.TryGetValue(propType,out local)) { local = il.DeclareLocal(propType); locals.Add(propType,local); } // need a ref to use it for the static-call il.Emit(OpCodes.Stloc,local); // [target][value] il.Emit(OpCodes.Ldloca,local); // [target][value][value*] var hasValue = propType.GetProperty("HasValue").GetGetMethod(); il.EmitCall(OpCodes.Call,hasValue,null); // [target][value][value.HasValue] } il.Emit(OpCodes.Brtrue_S,nonNull); // [target][value] il.Emit(OpCodes.Pop); // [target] il.Emit(OpCodes.Pop); // nix il.Emit(OpCodes.Br_S,end); // nix il.MarkLabel(nonNull); // (incoming) [target][value] il.EmitCall(OpCodes.Callvirt,prop.GetSetMethod(),null); // nix il.MarkLabel(end); // (incoming) nix } il.Emit(OpCodes.Ret); merge = (Action<T,T>)method.CreateDelegate(typeof(Action<T,T>)); } } }