C# 正在寻找一种快速简便的方法来合并POCO上的所有属性

C# 正在寻找一种快速简便的方法来合并POCO上的所有属性,c#,reflection,C#,Reflection,我有一些普通的旧类,它们有一些简单的属性(简单的{get;set;}声明)。所有属性都可以为空(或等效为引用类型) 例如: class POCO { int? Field1 { get; set; } string Field2 { get; set; } ... etc ... } 我有一个场景,我在逐段构建这些POCO,最后我想得到一个包含所有非空字段的POCO 一些说明性代码: POCO o1 = LoadFields1To3(); POCO o2 = LoadFields4

我有一些普通的旧类,它们有一些简单的属性(简单的
{get;set;}
声明)。所有属性都可以为空(或等效为引用类型)

例如:

class POCO
{
  int? Field1 { get; set; }
  string Field2 { get; set; }
  ... etc ...
}
我有一个场景,我在逐段构建这些POCO,最后我想得到一个包含所有非空字段的POCO

一些说明性代码:

POCO o1 = LoadFields1To3();
POCO o2 = LoadFields4To5();
POCO o3 = LoadFields6To9();
... etc ...
我们之所以使用这种场景,是因为有些字段是从SQL(有时是不同的查询)加载的,而有些字段是从内存中的数据结构加载的。我在这里使用POCO类型是为了避免一堆毫无意义的类(静态类型对于Dapper非常有用,只是一般情况下)

我要寻找的是一种很好的方法,可以将这些对象的属性合并成一个具有非空属性的对象

比如:

POCO final = o1.UnionProperties(o2).UnionProperties(o3) // and so on
我能够保证在多个对象上没有字段是非空的。虽然我假设一个解决方案将采用最左边的非空字段,但实际上这并不是必需的

我知道我可以编写一些反射代码来实现这一点,但它有点令人讨厌和缓慢

这确实需要在一般情况下适用,因为虽然我从未打算合并不同类型的对象,但此方法将适用于大量类型

我想知道是否有更聪明的方法,可能滥用动态?

我收集(好的,我问过你)这里的主要目标是:

  • 性能(反射似乎太慢)
  • 低维护(希望避免非常手动的复制方法或复杂的属性)
以下内容使用元编程在运行时执行任何可以执行的操作,将自身编译为类型化委托(
操作
),以便高效重用:

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>));
        }
    }
}
使用系统;
使用System.Collections.Generic;
使用System.Reflection.Emit;
公共类抽样
{
公共int?Field1{get;set;}
公共字符串字段2{get;set;}
//这里有越来越多的房产
}
静态类程序
{
静态void Main()
{
var obj1=新样本{Field1=123};
var obj2=new SamplePoco{Field2=“abc”};
var merged=合并。合并(obj1,obj2);
Console.WriteLine(合并的.Field1);
Console.WriteLine(合并的.Field2);
}
}
静态类合并
{
公共静态T合并(参数T[]源),其中T:class,new()
{
var merge=MergerImpl.merge;
var obj=新的T();
对于(inti=0;i
他不是说他在寻找一种不令人讨厌的方式吗?:)@阿费金是主观的;有时这只是一个简单的例子,将讨厌的东西移动到一个中心点,在那里它可以潜伏而不碍事,而不是用讨厌的东西填充整个代码库。@afeygin-Marc的解释是正确的,“我不希望到处散布大量的锅炉板,而是希望有一个包含像th-哦,上帝,我的眼睛这样的代码的文件。@MarcGravel尝试在源代码是propertyInfo.GetValue()的结果的地方使用它,我在设计时不知道PropertyType。我只调用Merge.Merge(),没有泛型,因此它使用
对象
,但是,如果我调用
源[0].GetType().Name
,则它的类型正确。我想不出一个解决办法。有什么建议吗?此外,您手头上没有递归实现,是吗?:)