一個高性能的對象屬性複製類,支持不一樣類型對象間複製,支持Nullable類型屬性

因爲在實際應用中,須要對大量的對象屬性進行復制,原來的方法是經過反射實現,在量大了之後,反射的性能問題就凸顯出來了,必須用Emit來實現。程序員

搜了一圈代碼,沒發現適合的,要麼只能在相同類型對象間複製,要麼不支持Nullable<T>類型的屬性。沒辦法,本身幹吧,一邊查資料一邊堆IL,終於測試經過。緩存

本類支持在不一樣類型的對象之間複製屬性值,也支持同名但不一樣類型的屬性之間複製,好比從 string 類型複製到 int 類型,從 int 類型複製到 int? 類型。ide

 

測試代碼以下:函數

先定義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 優化

 

最後,貼上覆制類的源碼:ui

/*
 * 版權及免責申明:
 * 本程序代碼由「程序員海風」開發,並以「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時出錯的問題。spa

完。code

相關文章
相關標籤/搜索