【问题标题】:Dynamic object property populator (without reflection)动态对象属性填充器(无反射)
【发布时间】:2010-11-19 09:41:52
【问题描述】:

我想以类似于DynamicBuilder on CodeProject 的方式在不使用反射的情况下填充对象的属性。 CodeProject 示例专为使用 DataReader 或 DataRecord 填充实体而设计。我在几个 DAL 中使用它以获得良好的效果。现在我想修改它以使用字典或其他与数据无关的对象,以便我可以在非 DAL 代码中使用它——我目前使用反射的地方。我对 OpCodes 和 IL 几乎一无所知。我只知道它工作得很好,而且比反射更快。

我试图修改 CodeProject 示例,但由于我对 IL 的无知,我被困在了两条线上。

  • 其中一个处理 dbnulls,我很确定我可能会丢失它,但我不知道它之前和之后的行是否相关以及它们中的哪些也需要删除。
  • 我认为,另一个是之前从数据记录中提取值的那个,现在需要将其从字典中提取出来。我想我可以用我的“property.Value”替换“getValueMethod”,但我不确定。

我也愿意为这只猫剥皮的替代/更好的方法。

这是到目前为止的代码(注释掉的行是我坚持的那些):

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;
    }
}

编辑:

使用 Marc Gravell 的 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() 性能注意事项的 cmets...

【问题讨论】:

    标签: c# opcode ilgenerator


    【解决方案1】:

    编辑:所有这些基本上都是 dapper 所做的 - 但 dapper 更加优化。如果我今天写这个答案,它会简单地写成:“use dapper”。


    如果您对 IL 不是很“满意”,还有一些替代方案可以让您获得 IL 的速度和反射的便利性。

    第一个例子:

    HyperDescriptor - 使用自定义 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 上讨论过这个(以及上面的内容) - 请参阅 this archive

    【讨论】:

    • 太棒了!你能详细说明我为什么需要启用 HyperDescriptor 吗?我在这里修改了您的代码示例以摆脱数据读取器,并且我已经模拟了使用和不使用 HyperDescriptor 的测试。因为,我只想使用公共访问器设置属性,所以我似乎不需要 HyperDescriptor。 ——或者我错过了什么?
    • HyperDescriptor 是让它变得快速的神奇粘合剂。否则,它本质上是反射包裹。 HyperDescriptor 编写自定义 IL 因此您不必,并使其看起来完全像常规的 PropertyDescriptor 模型。这很好。
    • 可以通过使用动态关键字来改进吗?
    • @Harry - 我对此表示怀疑。除非您事先知道成员名称,否则您不能真正使用dynamic。如果您确实了解它们,那么您一开始就不会使用反射;p 简单地说,dynamic 对于这些库类型的实用程序函数并不是很有用。
    • @Marc 也这么想——但我总是认为某些巫师知道得更好。感谢您的代码。它确实有助于提高瓶颈中的性能
    【解决方案2】:

    是的,你可以使用这样的代码:

     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();
    

    .... ...

    【讨论】:

      猜你喜欢
      • 2013-10-08
      • 2011-09-12
      • 1970-01-01
      • 1970-01-01
      • 2015-06-21
      • 2020-04-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多