Ищете быстрый и простой способ объединить все свойства в POCO - PullRequest
14 голосов
/ 15 сентября 2011

У меня есть несколько простых классов с кучей простых свойств (простые объявления {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 и вообще в целом).

То, что я ищу, это хороший способ объединить свойства этих объектов в один со свойствами, не равными NULL.

Что-то вроде:

POCO final = o1.UnionProperties(o2).UnionProperties(o3) // and so on

Я могу гарантировать, что ни одно поле не является ненулевым для более чем одного объекта. Хотя я бы предположил, что решение заняло бы самое левое ненулевое поле, на самом деле в этом нет необходимости.

Я знаю, что могу написать некоторый код для отражения, но это немного неприятно и медленно.

Это должно быть применимо в общем случае, так как, хотя я никогда не собираюсь объединять объекты разных типов, существует очень большое количество типов, к которым этот метод применим.

Мне было интересно, нет ли более умного способа, возможно ab с использованием динамического?

1 Ответ

16 голосов
/ 15 сентября 2011

Я понимаю (хорошо, я вас спрашивал), что ключевыми задачами здесь являются:

  • производительность (отражение кажется слишком медленным)
  • низкие эксплуатационные расходы (хочу избежать очень ручногометод копирования или сложные свойства)

Следующее использует метапрограммирование, чтобы делать все возможное на лету во время выполнения, компилируя себя в типизированный делегат (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>));
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...