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

最近实践一个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 是一个可选参数,可以不提供。

----------------------------分界线-----------------------------------------------

本文能够写成,特别感谢网友 “泥水佬”和“海华”的支持,他们在关键思路上提供了帮助。

欢迎加入PDF.NET开源技术团队,做最快最轻的数据框架!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏钟绍威的专栏

从源代码到Runtime发生的重排序编译器重排序指令重排序内存系统重排序阻止重排序

 源代码和Runtime时执行的代码很可能不一样,这是因为编译器、处理器常常会为了追求性能对改变执行顺序。然而改变顺序执行很危险,很有可能使得运行结果和预想的不...

31590
来自专栏web前端教室

浏览器缓存是嘛?

浏览器的缓存这个东西,我是又爱又恨。爱的是它可以存一些东西在客户端节省资源、提高效率;恨的是你在缓存有时会造成用户那边的信息不更新,你这些修改了,那边收不到。尤...

22660
来自专栏DevOps时代的专栏

认识高性能Web缓存体系,你需要知道这些

前言 我们再看知识体系的时候,我们学一个东西的时候,每次我们都回过头去看一看,这就是所谓的不忘初心。这个说着容易做起来难,当一个人慢慢在成长,在进步的时候,是很...

34570
来自专栏我就是马云飞

LruCache源码解析

 今天我们来聊聊缓存策略相关的内容,LruCache应该说是三级缓存策略会使用到的内存缓存策略。今天我们就来扒一扒这里面的原理,同时也温故温故我们的数据结构方面...

28570
来自专栏钟绍威的专栏

内存屏障保证缓存一致性优化

 在前面内存系统重排序提到,“写缓存没有及时刷新到内存,导致不同处理器缓存的值不一样”,出现这种情况是糟糕的,所幸处理器遵循缓存一致性协议能够保证足够的可见性又...

49890
来自专栏冷冷

缓存策略优化

缓存介绍 这里是列表文本在高并发多用户的系统中常常会使用缓存来提升读写性能 这里是列表文本常见的如memcached, redis, 内存缓存等 ...

28980
来自专栏企鹅号快讯

Webpack 持久化缓存实践

作者:happylindz https://github.com/happylindz/blog/issues/7 前言 最近在看 webpack 如何做持久化...

38150
来自专栏冷冷

【Spring Cloud】Redis缓存接入监控、运维平台CacheCloud

CacheCloud CacheCloud提供一个Redis云管理平台:实现多种类型(Redis Standalone、Redis Sentinel、Redis...

45270

扩展CakePHP的CacheHelper以使用缓存引擎

CakePHP是一个MVC设计模式下的PHP框架,它使得您的生活更加简单并且让您的开发工作更上一层楼。尽管它被认为是一个相对缓慢的框架,(因为)它带有的大量缓存...

25390
来自专栏钟绍威的专栏

jvm内存溢出分析内存溢出是什么?内存溢出和内存泄漏有什么区别?用到的jvm参数分析解决方法分析

概述 jvm中除了程序计数器,其他的区域都有可能会发生内存溢出 内存溢出是什么? 当程序需要申请内存的时候,由于没有足够的内存,此时就会抛出OutOfMemor...

42650

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励