【问题标题】:Looking for a fast and easy way to coalesce all properties on a POCO寻找一种快速简便的方法来合并 POCO 上的所有属性
【发布时间】:2011-09-14 20:55:08
【问题描述】:

我有一些带有一堆简单属性的普通类(简单的{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

我能够保证在多个对象上没有一个字段是非空的。虽然我认为解决方案会采用最左边的非空字段,但实际上没有必要。

我知道我可以编写一些反射代码来做到这一点,但它有点讨厌和慢。

这确实需要普遍适用,因为虽然我从不打算合并不同类型的对象,但此方法适用于大量类型。

我想知道是否有更聪明的方法,也许ab使用动态?

【问题讨论】:

    标签: c# reflection


    【解决方案1】:

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

    • 性能(反射似乎太慢)
    • 维护成本低(希望避免非常手动的复制方法或复杂的属性)

    以下使用元编程在运行时动态执行任何操作,将自身编译为类型化委托 (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>));
            }
        }
    }
    

    【讨论】:

    • 他不是说他在寻找一种不讨厌的方式吗? :)
    • @afeygin 讨厌是主观的;有时这只是将 nasty 移动到一个可以潜伏而不受阻碍的单个中心点的情况,而不是用 nasty 填充整个代码库。
    • @afeygin- Marc 的解释是正确的,我不想到处散布大量样板文件,而是有一个包含类似代码的文件- 天哪,我的眼睛.
    • @MarcGravell 尝试使用它,其中源是 propertyInfo.GetValue() 的结果,我在设计时不知道 PropertyType。我只是调用没有泛型的 Merger.Merge(),所以它使用 object,但是,如果我使用 sources[0].GetType().Name,它的类型是正确的。我想不出办法解决这个问题。有什么建议么?另外,您没有方便的递归实现吗? :)
    猜你喜欢
    • 2014-01-09
    • 1970-01-01
    • 2013-12-16
    • 1970-01-01
    • 2010-10-17
    • 2016-06-09
    • 1970-01-01
    • 2020-01-07
    • 1970-01-01
    相关资源
    最近更新 更多