文档章节

使用反射+缓存+委托,实现一个不同对象之间同名同类型属性值的快速拷贝

李朝强
 李朝强
发布于 2014/02/28 17:19
字数 1694
阅读 422
收藏 20

最近实践一个DDD项目,在领域层与持久层之间,Domain Model与Entity Model之间有时候需要进行属性值得拷贝,而这些属性,尽管它所在的类名称不一样,但它们的属性名和属性类型差不多都是一样的。系统中有不少这样的 Model需要相互转换,有朋友推荐使用AutoMapper,试了下果然不错,解决了问题,但作为一个老鸟,决定研究下实现原理,于是动手也来山寨一 个。
为了让这个“轮子”尽量有实用价值,效率肯定是需要考虑的,所以决定采用“反射+缓存+委托”的路子。

第一次使用,肯定要反射出来对象的属性,这个简单,就下面的代码:

Type targetType;//....PropertyInfo[] targetProperties = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance);

这里只获取公开的实例对象的属性。

要实现同名同类型的属性拷贝,那么需要把这些属性找出来,下面是完整的代码:

复制代码

 public ModuleCast(Type sourceType, Type targetType)
        {
            PropertyInfo[] targetProperties = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance);            foreach (PropertyInfo sp in sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {                foreach (PropertyInfo tp in targetProperties)
                {                    if (sp.Name == tp.Name && sp.PropertyType == tp.PropertyType)
                    {
                        CastProperty cp = new CastProperty();
                        cp.SourceProperty = new PropertyAccessorHandler(sp);
                        cp.TargetProperty = new PropertyAccessorHandler(tp);
                        mProperties.Add(cp);                        break;
                    }
                }
            }
        }

复制代码

这里使用了一个 CastProperty 类来保存要处理的源对象和目标对象,并且把这组对象放到一个CastProperty 列表的mProperties 静态对象里面缓存起来。
下面是 CastProperty 类的定义:

复制代码

/// <summary>
        /// 转换属性对象        /// </summary>
        public class CastProperty
        {            public PropertyAccessorHandler SourceProperty
            {                get;                set;
            }            public PropertyAccessorHandler TargetProperty
            {                get;                set;
            }
        }

复制代码

类本身很简单,关键就是这个属性访问器PropertyAccessorHandler 对象,下面是它的定义:

复制代码

 /// <summary>
        /// 属性访问器        /// </summary>
        public class PropertyAccessorHandler
        {            public PropertyAccessorHandler(PropertyInfo propInfo)
            {                this.PropertyName = propInfo.Name;                //var obj = Activator.CreateInstance(classType);                //var getterType = typeof(FastPropertyAccessor.GetPropertyValue<>).MakeGenericType(propInfo.PropertyType);                //var setterType = typeof(FastPropertyAccessor.SetPropertyValue<>).MakeGenericType(propInfo.PropertyType);                //this.Getter = Delegate.CreateDelegate(getterType, null, propInfo.GetGetMethod());                //this.Setter = Delegate.CreateDelegate(setterType, null, propInfo.GetSetMethod());

                if (propInfo.CanRead)                    this.Getter = propInfo.GetValue;                if (propInfo.CanWrite)                    this.Setter = propInfo.SetValue;
            }            public string PropertyName { get; set; }            public Func<object, object[], object> Getter { get; private set; }            public Action<object, object, object[]> Setter { get; private set; }
        }

复制代码

在写这个类的时候,曾经走了好几次弯路,前期准备通过 Delegate.CreateDelegate 方式创建一个当前属性Get和Set方法的委托,但是经过数次测试发现,
Delegate.CreateDelegate(getterType, obj, propInfo.GetGetMethod());

这里的obj 要么是一个对象实例,要么是null,如果是null,那么这个委托定义只能绑定到类型的静态属性方法上;如果不是null,那么这个委托只能绑定到当前 obj 实例对象上,换句话说,如果将来用obj类型的另外一个实例对象,那么这个委托访问的还是之前那个obj 对象,跟新对象实例无关。
PS:为了走这条“弯路”,前几天还特意写了一个FastPropertyAccessor,申明了2个泛型委托,来绑定属性的Get和Set方法,即上面注释掉的2行代码:

 var getterType = typeof(FastPropertyAccessor.GetPropertyValue<>).MakeGenericType(propInfo.PropertyType); var setterType = typeof(FastPropertyAccessor.SetPropertyValue<>).MakeGenericType(propInfo.PropertyType);

好不容易将这个泛型委托创建出来了,编译也通过了,却发现最终没法使用,别提有多郁闷了:-《

回归话题,有了PropertyAccessorHandler,那么我们只需要遍历当前要转换的目标类型的属性集合,就可以开始对属性进行拷贝了:

复制代码

 public void Cast(object source, object target)
        {            if (source == null)                throw new ArgumentNullException("source");            if (target == null)                throw new ArgumentNullException("target");            for (int i = 0; i < mProperties.Count; i++)
            {
                CastProperty cp = mProperties[i];                if (cp.SourceProperty.Getter != null)
                {                    object Value = cp.SourceProperty.Getter(source, null); //PropertyInfo.GetValue(source,null);
                    if (cp.TargetProperty.Setter != null)
                        cp.TargetProperty.Setter(target, Value, null);// PropertyInfo.SetValue(target,Value ,null);                }
            }
        }

复制代码

上面的代码会判断属性的Set访问器是否可用,可用的话才复制值,所以可以解决“只读属性”的问题。

注意:这里只是直接复制了属性的值,对应的引用类型而言自然也只是复制了属性的引用,所以这是一个“浅表拷贝”。

现在,主要的代码都有了,因为我们缓存了执行类型对象的属性访问方法的委托,所以我们的这个“属性值拷贝程序”具有很高的效率,有关委托的效率测试,在前一篇
《使用泛型委托,构筑最快的通用属性访问器》 http://www.cnblogs.com/bluedoctor/archive/2012/12/18/2823325.html
已经做了测试,大家可以去看看测试结果,缓存后的委托方法,效率非常高的。

为了让该小程序更好用,又写了个扩展方法,让Object类型的对象都可以方便的进行属性值拷贝

复制代码

    /// <summary>
    /// 对象转换扩展    /// </summary>
    public static class ModuleCastExtension
    {        /// <summary>
        /// 将当前对象的属性值复制到目标对象,使用浅表复制        /// </summary>
        /// <typeparam name="T">目标对象类型</typeparam>
        /// <param name="source">源对象</param>
        /// <param name="target">目标对象,如果为空,将生成一个</param>
        /// <returns>复制过后的目标对象</returns>
        public static T CopyTo<T>(this object source, T target = null) where T : class,new()
        {            if (source == null)                throw new ArgumentNullException("source");            if (target == null)
                target = new T();
            ModuleCast.GetCast(source.GetType(), typeof(T)).Cast(source, target);            return target;
        }
    }

复制代码

这样,该小程序可以象下面以几种不同的形式来使用了:

复制代码

         //      下面几种用法一样:
         ModuleCast.GetCast(typeof(CarInfo), typeof(ImplCarInfo)).Cast(info, ic);
         ModuleCast.CastObject<CarInfo, ImplCarInfo>(info, ic);
         ModuleCast.CastObject(info, ic);
    
        ImplCarInfo icResult= info.CopyTo<ImplCarInfo>(null);
  
         ImplCarInfo icResult2 = new ImplCarInfo();
         info.CopyTo<ImplCarInfo>(icResult2);

复制代码

完整的代码下载,请看这里

补充:

经网友使用发现,需要增加一些不能拷贝的属性功能,下面我简单的改写了下原来的代码(这些代码没有包括在上面的下载中):

复制代码

/// <summary>
        /// 将源类型的属性值转换给目标类型同名的属性        /// </summary>
        /// <param name="source"></param>
        /// <param name="target"></param>
        public void Cast(object source, object target)
        {
            Cast(source, target, null);
        }        /// <summary>
        /// 将源类型的属性值转换给目标类型同名的属性,排除要过滤的属性名称        /// </summary>
        /// <param name="source"></param>
        /// <param name="target"></param>
        /// <param name="filter">要过滤的属性名称</param>
        public void Cast(object source, object target,string[] filter)
        {            if (source == null)                throw new ArgumentNullException("source");            if (target == null)                throw new ArgumentNullException("target");            for (int i = 0; i < mProperties.Count; i++)
            {
                CastProperty cp = mProperties[i];                
                if (cp.SourceProperty.Getter != null)
                {                    object Value = cp.SourceProperty.Getter(source, null); //PropertyInfo.GetValue(source,null);
                    if (cp.TargetProperty.Setter != null)
                    {                        if (filter == null)
                            cp.TargetProperty.Setter(target, Value, null);                        else if (!filter.Contains(cp.TargetProperty.PropertyName))
                            cp.TargetProperty.Setter(target, Value, null);
                    
                    }
                }
            }
        }

复制代码

然后这修改一下那个扩展方法:

复制代码

 public static T CopyTo<T>(this object source, T target = null,string[] filter=null) where T : class,new()
        {            if (source == null)                throw new ArgumentNullException("source");            if (target == null)
                target = new T();
            ModuleCast.GetCast(source.GetType(), typeof(T)).Cast(source, target, filter);            return target;
        }

复制代码

 

最后,这样调用即可:

复制代码

    class Program
    {        static void Main(string[] args)
        {
            A a = new A() {  Name="aaa", NoCopyName="no.no.no."};            var b = a.CopyTo<B>(filter: new string[] { "NoCopyName" });
        }
    }    class A
    {       public string Name { get; set; }       public string NoCopyName { get; set; }       public DateTime GetTime { get { return DateTime.Now; } }
    }    class B
    {        public string Name { get; set; }        public string NoCopyName { get; set; }        public DateTime GetTime { get { return DateTime.Now; } }
    }

复制代码

 

filter 是一个可选参数,可以不提供。


本文转载自:http://www.cnblogs.com/bluedoctor/archive/2012/12/20/2826392.html

李朝强
粉丝 91
博文 297
码字总数 149962
作品 0
郑州
产品经理
私信 提问
加载中

评论(1)

A
AmwITx
mark
为什么 .NET 的反射这么慢?

大家都知道 .NET 的反射很慢,但是为什么会出现这种情况呢?这篇文章会带你寻找这个问题的真正原因。 CLR 类型系统的设计目标 原因之一是,在设计的时候反射本身就不是以高性能为目标的,可以...

oschina
2016/12/19
7K
31
C# 使用反射获取私有属性的方法

版权声明:博客已迁移到 https://blog.lindexi.com 欢迎访问。如果当前博客图片看不到,请到 https://blog.lindexi.com 访问博客。本文地址 https://blog.csdn.net/lindexi_gd/article/detai...

lindexi_gd
04/16
0
0
晚绑定场景下对象属性赋值和取值可以不需要PropertyInfo

在《一句代码实现批量数据绑定》中,我通过界面控件ID与作为数据源的实体属性名之间的映射实现了批量数据绑定。由于里面频繁涉及对属性的反射——通过反射从实体对象中获取某个属性值;通过反...

长平狐
2012/09/04
272
0
JavaScript 创建对象: 方法一览与最佳实践

打开双语对照阅读 在JavaScript中“创建对象”是一个复杂的话题。这门语言提供了很多种创建对象的方式,不论新手还是老手都可能对此感到无所适从,不知道应该选择哪一种。不过,尽管创建对象...

Yomut
2016/08/03
11
0
Go 语言教程(4)——数据结构

Array 和其他语言的数组不同。 数组是值类型,赋值和传参会复制整个数组,而不是指针。 数组长度必须是常量,且是类型的组成部分。 和 是不同类型。 支持 、 操作符,因为内存总是被初始化过...

流年1004
2018/01/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring使用ThreadPoolTaskExecutor自定义线程池及实现异步调用

多线程一直是工作或面试过程中的高频知识点,今天给大家分享一下使用 ThreadPoolTaskExecutor 来自定义线程池和实现异步调用多线程。 一、ThreadPoolTaskExecutor 本文采用 Executors 的工厂...

CREATE_17
今天
5
0
CSS盒子模型

CSS盒子模型 组成: content --> padding --> border --> margin 像现实生活中的快递: 物品 --> 填充物 --> 包装盒 --> 盒子与盒子之间的间距 content :width、height组成的 内容区域 padd......

studywin
今天
7
0
修复Win10下开始菜单、设置等系统软件无法打开的问题

因为各种各样的原因导致系统文件丢失、损坏、被修改,而造成win10的开始菜单、设置等系统软件无法打开的情况,可以尝试如下方法解决 此方法只在部分情况下有效,但值得一试 用Windows键+R打开...

locbytes
昨天
8
0
jquery 添加和删除节点

本文转载于:专业的前端网站➺jquery 添加和删除节点 // 增加一个三和一节点function addPanel() { // var newPanel = $('.my-panel').clone(true) var newPanel = $(".triple-panel-con......

前端老手
昨天
8
0
一、Django基础

一、web框架分类和wsgiref模块使用介绍 web框架的本质 socket服务端 与 浏览器的通信 socket服务端功能划分: 负责与浏览器收发消息(socket通信) --> wsgiref/uWsgi/gunicorn... 根据用户访问...

ZeroBit
昨天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部