• 一个高性能的对象属性复制类,支持不同类型对象间复制,支持Nullable<T>类型属性


    由于在实际应用中,需要对大量的对象属性进行复制,原来的方法是通过反射实现,在量大了以后,反射的性能问题就凸显出来了,必须用Emit来实现。

    搜了一圈代码,没发现适合的,要么只能在相同类型对象间复制,要么不支持Nullable<T>类型的属性。没办法,自己干吧,一边查资料一边堆IL,终于测试通过。

    本类支持在不同类型的对象之间复制属性值,也支持同名但不同类型的属性之间复制,比如从 string 类型复制到 int 类型,从 int 类型复制到 int? 类型。

    测试代码如下:

    先定义2个不同类型的实体类,他们有同名却不同类型的属性。

    public class Obj1
    {
        public string aa { get; set; }
     
        public string bb { get; set; }
     
        public DateTime? cc { get; set; }
     
        public bool? dd { get; set; }
     
        public int? ee { get; set; }
    }
     
    public class Obj2
    {
        public string aa { get; set; }
     
        public int? bb { get; set; }
     
        public DateTime cc { get; set; }
     
        public bool? dd { get; set; }
     
        public int ee { get; set; }
     
     
    }

    测试代码:

                    Obj1 o1 = new Obj1();
                    o1.aa = "fdsfds";
                    o1.bb = "1";
                    o1.cc = DateTime.Now;
                    o1.dd = true;
                    o1.ee = 3;
    
                    Obj2 o2 = new Obj2();
                    o2.aa = "aaa";
                    o2.dd = true;
    
                    Obj2 o3 = ObjectCopier.Copy<Obj1, Obj2>(o1, o2);

    运行之后,Obj1的属性被完整的复制到Obj2 

    最后,贴上复制类的源码:

    /*
     * 版权及免责申明:
     * 本程序代码由“程序员海风”开发,并以“BSD协议”对外发布,你无需为使用本程序代码而向作者付费,但请勿删除本段版权说明。
     * 本程序代码以现状提供,作者不对因使用本程序代码而造成的任何结果负责。
     * 
     * 作者的BLOG:http://www.cnblogs.com/hhh/
     * 作者的PRESS.one:https://press.one/main/p/5475a03ae8011091fc2b98de6d3b181eb9f447df
     */
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Linq;
    using System.Text;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Collections;
    
    namespace Haifeng
    {
        /// <summary>
        /// 对象复制器
        /// </summary>
        public class ObjectCopier
        {
            //把T1转换为T2
            public delegate T2 ConvertObject<T1, T2>(T1 obj1, T2 obj2);
    
            //动态方法缓存
            private static Hashtable caches = new Hashtable();
    
            /// <summary>
            /// 复制对象的属性值到另一个对象
            /// </summary>
            /// <param name="sourceObj">原对象</param>
            /// <param name="targetObj">目标对象</param>
            public static T2 Copy<T1, T2>(T1 sourceObj, T2 targetObj)
            {
                StringCollection sc = new StringCollection();
                return Copy<T1, T2>(sourceObj, targetObj, sc);
            }
    
            /// <summary>
            /// 复制对象的属性值到另一个对象
            /// </summary>
            /// <param name="sourceObj">原对象</param>
            /// <param name="targetObj">目标对象</param>
            /// <param name="ignoreProperties">忽略的属性</param>
            public static T2 Copy<T1, T2>(T1 sourceObj, T2 targetObj, StringCollection ignoreProperties)
            {
                if (sourceObj == null)
                {
                    throw new ArgumentNullException("sourceObj");
                }
                if (targetObj == null)
                {
                    throw new ArgumentNullException("targetObj");
                }
    
                ConvertObject<T1, T2> load = GetObjectMethod<T1, T2>(ignoreProperties);
                return load(sourceObj, targetObj);
            }
    
            /// <summary>
            /// 获取复制T1的属性值到T2的动态方法
            /// </summary>
            /// <typeparam name="T1">原对象</typeparam>
            /// <typeparam name="T2">目标对象</typeparam>
            /// <param name="ignoreProperties">要跳过的属性名</param>
            /// <returns></returns>
            private static ConvertObject<T1, T2> GetObjectMethod<T1, T2>(StringCollection ignoreProperties)
            {
                string key = "Convert" + typeof(T1).Name + "To" + typeof(T2).Name;
                foreach (string str in ignoreProperties)
                    key += str;
    
                ConvertObject<T1, T2> load = null;
                if (caches[key] == null)
                {
                    load = (ConvertObject<T1, T2>)BuildMethod<T1, T2>(ignoreProperties).CreateDelegate(typeof(ConvertObject<T1, T2>));
                    caches.Add(key, load);
                }
                else
                {
                    load = caches[key] as ConvertObject<T1, T2>;
                }
                return load;
            }
    
            private static DynamicMethod BuildMethod<T1, T2>(StringCollection ignoreProperties)
            {
                Type sourceType = typeof(T1);
                Type targetType = typeof(T2);
                string methodName = "Convert" + sourceType.Name + "To" + targetType.Name;
                foreach (string str in ignoreProperties)
                    methodName += str;
    
                DynamicMethod method = new DynamicMethod(methodName, MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, targetType,
                        new Type[] { sourceType, targetType }, typeof(ObjectCopier).Module, true);
                ILGenerator generator = method.GetILGenerator();
    
                //遍历目标对象的属性
                foreach (PropertyInfo targetProperty in targetType.GetProperties())
                {
                    //自定义跳过的属性
                    if (ignoreProperties.Contains(targetProperty.Name))
                        continue;
                    //原对象没有相应的属性,跳过
                    PropertyInfo srcProperty = sourceType.GetProperty(targetProperty.Name);
                    if (srcProperty == null)
                        continue;
                    //获取原对象属性的get方法,如果不存在也跳过
                    MethodInfo getMethod = sourceType.GetMethod("get_" + srcProperty.Name);
                    if (getMethod == null)
                        continue;
    
                    Type sourcePropertyType = srcProperty.PropertyType;            //原对象的属性类型
                    Type targetPropertyType = targetProperty.PropertyType;         //目标对象的属性类型
    
                    var label1 = generator.DefineLabel();                          //用于继续执行的label
                    var labelEnd = generator.DefineLabel();                        //跳过执行的label
    
                    generator.Emit(OpCodes.Ldarg_1);                               // 参数1压栈,参数1是目标对象
                    generator.Emit(OpCodes.Ldarg_0);                               // 参数0压栈,参数0是原对象
                    generator.Emit(OpCodes.Callvirt, getMethod);                   // 在原对象上调用 'get_属性名' 方法,取出属性值
    
                    if (sourcePropertyType.IsValueType)
                    {
                        //如果是Nullable<T>类型,要判断是否为空,为空要跳过
                        if (sourcePropertyType.IsGenericType && sourcePropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                        {
                            var temp_value = generator.DeclareLocal(sourcePropertyType);                          //申明一个变量,类型是Nullable<T>
                            var hasValue_Method = sourcePropertyType.GetMethod("get_HasValue", Type.EmptyTypes);  //判断是否为空的方法
                            var hasValueBool = generator.DeclareLocal(typeof(bool));                              //什么一个变量,bool类型
    
                            generator.Emit(OpCodes.Stloc, temp_value);                    //弹出堆栈的值到变量,类型是Nullable<T>
                            generator.Emit(OpCodes.Ldloca, temp_value);                   //把变量地址压栈
                            generator.Emit(OpCodes.Call, hasValue_Method);                //调用判断是否为空的方法
                            generator.Emit(OpCodes.Stloc, hasValueBool);                  //弹出堆栈到变量,类型是bool
                            generator.Emit(OpCodes.Ldloc, hasValueBool);                  //入栈,类型是bool
                            generator.Emit(OpCodes.Brtrue_S, label1);                     //跳转到继续执行
    
                            generator.Emit(OpCodes.Pop);                                  //弹出堆栈,弹出的是 OpCodes.Ldarg_1
                            generator.Emit(OpCodes.Br_S, labelEnd);                       //跳转到结束标签
    
                            generator.MarkLabel(label1);                                  //设定标签,继续执行的label
                            generator.Emit(OpCodes.Ldloc, temp_value);                    //压入变量 tempValue,此时堆栈顺序为 OpCodes.Ldarg_1 >> value_temp
                        }
    
                        generator.Emit(OpCodes.Box, sourcePropertyType);                  //值类型要装箱,不然会被编译器优化掉,此时堆栈为 OpCodes.Ldarg_1 >> object(value)
                    }
    
                    //此时堆栈顶端为 取出的属性值
    
                    //如果类型不一致,需要类型转换
                    if (sourcePropertyType != targetPropertyType)
                    {
                        //支持Nullable<T>类型的属性的复制
                        Type sourcePropertyUnderType = sourcePropertyType;  //Nullable<T> 的T的类型
                        Type targetPropertyUnderType = targetPropertyType;  //Nullable<T> 的T的类型
                        if (sourcePropertyType.IsGenericType && sourcePropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                            sourcePropertyUnderType = Nullable.GetUnderlyingType(sourcePropertyType);
                        if (targetPropertyType.IsGenericType && targetPropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                            targetPropertyUnderType = Nullable.GetUnderlyingType(targetPropertyType);
                        
                        //如果原始类型是Nullable<T>,先取出原始值
                        if (sourcePropertyType.IsGenericType && sourcePropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                        {
                            var temp_value = generator.DeclareLocal(sourcePropertyType);                  //申明一个变量,类型是Nullable<T>
                            var temp_under_value = generator.DeclareLocal(sourcePropertyUnderType);       //申明一个变量,类型是T
                            var temp_method = sourcePropertyType.GetMethod("get_Value", new Type[] { });  //Nullable<T>获取原始值的方法
    
                            generator.Emit(OpCodes.Unbox_Any, sourcePropertyType);                        //拆箱,因为Nullable<T>肯定是值类型的,所以前面肯定有装箱操作
                            generator.Emit(OpCodes.Stloc, temp_value);                                    //弹出堆栈的值到变量
                            generator.Emit(OpCodes.Ldloca, temp_value);                                   //把变量地址压栈
                            generator.Emit(OpCodes.Call, temp_method);                                    //在变量地址上调用方法,获取Nullable<T>的原始值
                            generator.Emit(OpCodes.Stloc, temp_under_value);                              //出栈,保存到变量
                            generator.Emit(OpCodes.Ldloc, temp_under_value);                              //变量入栈
    
                            if (sourcePropertyUnderType.IsValueType)                                      //原始值是值类型,要进行装箱操作,不然会被编译器优化掉
                            {
                                generator.Emit(OpCodes.Box, sourcePropertyUnderType);                     //这条100%会执行,因为Nullable<T>的T肯定是值类型
                            }
                        }
    
                        //此时堆栈顶端为取出的属性值;
                        //如果属性是Nullable<T>类型,那么此时堆栈顶端为Nullable<T>的原始值T
                        //下面进行属性值的类型转换
                        MethodInfo convert_method = GetConverterMethod(targetPropertyUnderType);          //获取类型转换的方法
                        if (convert_method != null)
                        {
                            generator.Emit(OpCodes.Call, convert_method);                                 //调用类型转换的方法
                            if (targetPropertyUnderType.IsValueType)                                      //如果目标类型是值类型
                            {
                                generator.Emit(OpCodes.Box, targetPropertyUnderType);                     //装箱,不然会被编译器扔掉
                            }
                        }
    
                        //此时堆栈顶端为属性值,已经转换成目标属性类型
                        //如果目标属性类型是 Nullable<T>,此时堆栈顶端的值是类型T
                        //如果目标类型是 Nullable<T>,需要转换成Nullable<T>
                        if (targetPropertyType.IsGenericType && targetPropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                        {
                            var ctor = targetPropertyType.GetConstructor(new Type[] { targetPropertyUnderType });  //取得Nullable<T>的构造函数方法,参数类型是T
                            generator.Emit(OpCodes.Unbox_Any, targetPropertyUnderType);                            //拆箱
                            generator.Emit(OpCodes.Newobj, ctor);                                                  //调用构造函数方法,得到 Nullable<T>
                            generator.Emit(OpCodes.Box, targetPropertyType);                                       //装箱,类型是 目标属性类型
                        }
    
                    }
    
                    //此时堆栈顶端应为转换成目标属性类型的,属性值
    
                    var target_value = generator.DeclareLocal(targetPropertyType);         //定义一个变量,类型为目标属性类型
                    generator.Emit(OpCodes.Unbox_Any, targetPropertyType);                 //拆箱
                    generator.Emit(OpCodes.Stloc, target_value);                           //出栈,堆栈顶端的值保存到变量
                    generator.Emit(OpCodes.Ldloc, target_value);                           //入栈,变量压入堆栈
    
                    MethodInfo setMethod = targetProperty.GetSetMethod();                  //目标对象属性的Set方法
                    generator.Emit(OpCodes.Call, setMethod);                               //调用Set方法,变量到目标对象属性
    
    
                    generator.MarkLabel(labelEnd);                                         //前面检查原属性值Nullable<T>是null,跳转到这里,相当于continue语句
                }
    
                generator.Emit(OpCodes.Ldarg_1);             //参数1压栈,参数1是目标对象
                generator.Emit(OpCodes.Ret);                 //方法返回
                return method;
            }
    
    
            private static MethodInfo GetConverterMethod(Type type)
            {
                switch (type.Name.ToUpper())
                {
                    case "INT16":
                        return CreateConverterMethodInfo("ToInt16");
                    case "INT32":
                        return CreateConverterMethodInfo("ToInt32");
                    case "INT64":
                        return CreateConverterMethodInfo("ToInt64");
                    case "SINGLE":
                        return CreateConverterMethodInfo("ToSingle");
                    case "BOOLEAN":
                        return CreateConverterMethodInfo("ToBoolean");
                    case "STRING":
                        return CreateConverterMethodInfo("ToString");
                    case "DATETIME":
                        return CreateConverterMethodInfo("ToDateTime");
                    case "DECIMAL":
                        return CreateConverterMethodInfo("ToDecimal");
                    case "DOUBLE":
                        return CreateConverterMethodInfo("ToDouble");
                    case "GUID":
                        return CreateConverterMethodInfo("ToGuid");
                    case "BYTE[]":
                        return CreateConverterMethodInfo("ToBytes");
                    case "BYTE":
                        return CreateConverterMethodInfo("ToByte");
                    case "NULLABLE`1":
                        {
                            if (type == typeof(DateTime?))
                            {
                                return CreateConverterMethodInfo("ToDateTimeNull");
                            }
                            else if (type == typeof(Int32?))
                            {
                                return CreateConverterMethodInfo("ToInt32Null");
                            }
                            else if (type == typeof(Boolean?))
                            {
                                return CreateConverterMethodInfo("ToBooleanNull");
                            }
                            else if (type == typeof(Int16?))
                            {
                                return CreateConverterMethodInfo("ToInt16Null");
                            }
                            else if (type == typeof(Int64?))
                            {
                                return CreateConverterMethodInfo("ToInt64Null");
                            }
                            else if (type == typeof(Single?))
                            {
                                return CreateConverterMethodInfo("ToSingleNull");
                            }
                            else if (type == typeof(Decimal?))
                            {
                                return CreateConverterMethodInfo("ToDecimalNull");
                            }
                            else if (type == typeof(Double?))
                            {
                                return CreateConverterMethodInfo("ToDoubleNull");
                            }
                            break;
                        }
                }
                return null;
            }
    
            private static MethodInfo CreateConverterMethodInfo(string method)
            {
                return typeof(MyConverter).GetMethod(method, new Type[] { typeof(object) });
            }
    
        }
    
    
        public static class MyConverter
        {
            public static Int16 ToInt16(object value)
            {
                return ChangeType<Int16>(value);
            }
    
            public static Int32 ToInt32(object value)
            {
                return ChangeType<Int32>(value);
            }
    
            public static Int64 ToInt64(object value)
            {
                return ChangeType<Int64>(value);
            }
    
            public static Single ToSingle(object value)
            {
                return ChangeType<Single>(value);
            }
    
            public static Boolean ToBoolean(object value)
            {
                return ChangeType<Boolean>(value);
            }
    
            public static System.String ToString(object value)
            {
                return ChangeType<System.String>(value);
            }
    
            public static DateTime ToDateTime(object value)
            {
                return ChangeType<DateTime>(value);
            }
    
            public static Decimal ToDecimal(object value)
            {
                return ChangeType<Decimal>(value);
            }
    
            public static Double ToDouble(object value)
            {
                return ChangeType<Double>(value);
            }
    
            public static Guid ToGuid(object value)
            {
                return ChangeType<Guid>(value);
            }
    
            public static Byte ToByte(object value)
            {
                return ChangeType<Byte>(value);
            }
    
            public static Byte[] ToBytes(object value)
            {
                return ChangeType<Byte[]>(value);
            }
            public static DateTime? ToDateTimeNull(object value)
            {
                return ChangeType<DateTime?>(value);
            }
    
            public static System.Int32? ToInt32Null(object value)
            {
                return ChangeType<Int32?>(value);
            }
    
            public static Boolean? ToBooleanNull(object value)
            {
                return ChangeType<Boolean?>(value);
            }
    
            public static Int16? ToInt16Null(object value)
            {
                return ChangeType<Int16?>(value);
            }
    
            public static Int64? ToInt64Null(object value)
            {
                return ChangeType<Int64?>(value);
            }
    
            public static Single? ToSingleNull(object value)
            {
                return ChangeType<Single?>(value);
            }
    
            public static Decimal? ToDecimalNull(object value)
            {
                return ChangeType<Decimal?>(value);
            }
    
            public static Double? ToDoubleNull(object value)
            {
                return ChangeType<Double?>(value);
            }
    
            private static T ChangeType<T>(object value)
            {
                if (value == null)
                {
                    return default(T);
                }
    
                var sourceType = value.GetType();
                var targetType = typeof(T);
                if (sourceType == targetType)
                {
                    return (T)value;
                }
    
                if (targetType.IsGenericType && targetType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
                {
                    targetType = Nullable.GetUnderlyingType(targetType);
                }
    
                T ret = default(T);
                try{
                    ret = (T)Convert.ChangeType(value, targetType);
                }
                catch { }
    
                return ret;
            }
        }
    
    
    }
    View Code

    2018-8-10更新,解决无法复制值类型属性,以及复制Nullable<T>类型属性=null时出错的问题。

    完。

  • 相关阅读:
    卸载软件
    SourceTree安装
    lombok安装及使用
    Element学习使用
    Vue基础学习
    SpringBoot与Swagger整合
    @Valid 数据校验 + 自定义全局异常信息
    Spring Boot 运行原理
    JNDI学习总结(一):JNDI到底是什么?
    Spring Boot AOP之对请求的参数入参与返回结果进行拦截处理
  • 原文地址:https://www.cnblogs.com/hhh/p/9130779.html
Copyright © 2020-2023  润新知