Заполнитель свойств динамического объекта (без отражения)

StackOverflow https://stackoverflow.com/questions/1273589

  •  16-09-2019
  •  | 
  •  

Вопрос

Я хочу заполнить свойства объекта без использования отражения, аналогично DynamicBuilder в CodeProject.Пример CodeProject предназначен для заполнения сущностей с помощью DataReader или DataRecord.Я использую это в нескольких DAL с хорошим эффектом.Теперь я хочу изменить его, чтобы использовать словарь или другой объект, независимый от данных, чтобы я мог использовать его в коде, отличном от DAL, - в местах, где я сейчас использую отражение.Я почти ничего не знаю об OpCodes и IL.Я просто знаю, что это работает хорошо и быстрее, чем отражение.

Я попытался изменить пример CodeProject, но из-за моего незнания IL застрял на двух строках.

  • Один из них касается dbnulls, и я почти уверен, что могу его просто потерять, но я не знаю, связаны ли между собой строки, предшествующие и следующие за ним, и какие из них тоже нужно будет пройти.
  • Другой, я думаю, тот, который раньше извлек значение из записи данных, а теперь должен извлечь его из словаря.Я думаю, что могу заменить «getValueMethod» своим «property.Value», но я не уверен.

Я также открыт для альтернативных/лучших способов снять шкуру с этой кошки.

Вот код на данный момент (закомментированные строки - это те, на которых я застрял):

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

public class Populator<T>
{
    private delegate T Load(Dictionary<string, object> properties);
    private Load _handler;
    private Populator() { }
    public T Build(Dictionary<string, object> properties)
    {
        return _handler(properties);
    }
    public static Populator<T> CreateBuilder(Dictionary<string, object> properties)
    {
        //private static readonly MethodInfo getValueMethod = typeof(IDataRecord).GetMethod("get_Item", new [] { typeof(int) });
        //private static readonly MethodInfo isDBNullMethod = typeof(IDataRecord).GetMethod("IsDBNull", new [] { typeof(int) });
        Populator<T> dynamicBuilder = new Populator<T>();
        DynamicMethod method = new DynamicMethod("Create", typeof(T), new[] { typeof(Dictionary<string, object>) }, typeof(T), true);
        ILGenerator generator = method.GetILGenerator();
        LocalBuilder result = generator.DeclareLocal(typeof(T));
        generator.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
        generator.Emit(OpCodes.Stloc, result);
        int i = 0;
        foreach (var property in properties)
        {
            PropertyInfo propertyInfo = typeof(T).GetProperty(property.Key, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy | BindingFlags.Default);
            Label endIfLabel = generator.DefineLabel();

            if (propertyInfo != null && propertyInfo.GetSetMethod() != null)
            {
                generator.Emit(OpCodes.Ldarg_0);
                generator.Emit(OpCodes.Ldc_I4, i);
                //generator.Emit(OpCodes.Callvirt, isDBNullMethod);
                generator.Emit(OpCodes.Brtrue, endIfLabel);

                generator.Emit(OpCodes.Ldloc, result);
                generator.Emit(OpCodes.Ldarg_0);
                generator.Emit(OpCodes.Ldc_I4, i);
                //generator.Emit(OpCodes.Callvirt, getValueMethod);

                generator.Emit(OpCodes.Unbox_Any, property.Value.GetType());
                generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());
                generator.MarkLabel(endIfLabel);
            }
            i++;
        }

        generator.Emit(OpCodes.Ldloc, result);
        generator.Emit(OpCodes.Ret);
        dynamicBuilder._handler = (Load)method.CreateDelegate(typeof(Load));
        return dynamicBuilder;
    }
}

РЕДАКТИРОВАТЬ:

Используя реализацию PropertyDescriptor Марка Гравелла (с HyperDescriptor), код упрощается в сто раз.Теперь у меня есть следующий тест:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using Hyper.ComponentModel;

namespace Test
{
    class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    class Program
    {
        static void Main()
        {
            HyperTypeDescriptionProvider.Add(typeof(Person));
            var properties = new Dictionary<string, object> { { "Id", 10 }, { "Name", "Fred Flintstone" } };
            Person person = new Person();
            DynamicUpdate(person, properties);
            Console.WriteLine("Id: {0}; Name: {1}", person.Id, person.Name);
            Console.ReadKey();
        }

        public static void DynamicUpdate<T>(T entity, Dictionary<string, object> properties)
        {
            foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(typeof(T)))
                if (properties.ContainsKey(propertyDescriptor.Name))
                    propertyDescriptor.SetValue(entity, properties[propertyDescriptor.Name]);
        }
    }
}

Любые комментарии по вопросам производительности для TypeDescriptor.GetProperties() и PropertyDescriptor.SetValue() приветствуются...

Это было полезно?

Решение

Редактировать:все это в основном то, что делает dapper, но dapper гораздо более оптимизирован.Если бы я писал этот ответ сегодня, он звучал бы просто:«используй щегольство».


Если вы не особо разбираетесь в IL, есть альтернативы, которые обеспечат вам скорость IL и удобство размышлений.

Первый пример:

Гипердескриптор - использует обычай PropertyDescriptor модель, которая работает с IL за вас, поэтому все, что у вас есть, это код (плюс однострочный код для включения HyperDescriptor):

public static IEnumerable<T> Read<T>(IDataReader reader) where T : class, new() 
{
    PropertyDescriptorCollection props =
        TypeDescriptor.GetProperties(typeof(T));

    PropertyDescriptor[] propArray = new PropertyDescriptor[reader.FieldCount];
    for (int i = 0; i < propArray.Length; i++)
    {
        propArray[i] = props[reader.GetName(i)];
    }
    while(reader.Read()) {
        T item = new T();
        for (int i = 0; i < propArray.Length; i++)
        {
            object value = reader.IsDBNull(i) ? null : reader[i];
            propArray[i].SetValue(item, value);
        }
        yield return item;
    }
}

Второй пример:

Выражения LINQ — довольно длинные, но я обсуждал это (и, как оказалось, выше) в Usenet — см. этот архив.

Другие советы

Да, вы можете использовать такой код:

 for (int i = 0; i < dataRecord.FieldCount; i++)
                {

                    PropertyInfo propertyInfo = t.GetProperty(dataRecord.GetName(i));
                    LocalBuilder il_P = generator.DeclareLocal(typeof(PropertyInfo));

                    Label endIfLabel = generator.DefineLabel();

.... ...

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top