使用 Emit 建立快速複製的代理函式

昨天示範了用 Expression Tree 作出一個快速的複製代理的程式

後來我想用同樣的結構,但改用 Emit 來寫出複製代理的程式

以下先上原始碼

        public static Action<TSource, TTarget> FastCloneProxy<TSource, TTarget>()
        {
            Type tsource = typeof(TSource);
            Type ttarget = typeof(TTarget);
            var dm = new DynamicMethod("clone", typeof(void), new Type[] { tsource, ttarget }, typeof(Program), true);
            var il = dm.GetILGenerator();
            var label = il.DefineLabel();
            FastCloneProxyNested(il, tsource, ttarget);
            il.Emit(OpCodes.Nop);
            il.Emit(OpCodes.Nop);
            il.Emit(OpCodes.Nop);
            il.Emit(OpCodes.Ret);
            return (Action<TSource, TTarget>)dm.CreateDelegate(typeof(Action<TSource, TTarget>));
        }
        
        private static void FastCloneProxyNested(ILGenerator il, Type source, Type target, List<MethodInfo> sourcePropertyMethods = null, List<MethodInfo> targetPropertyMethods = null)
        {
            var topLevelType =
                (
                    source.IsAssignableFrom(target) ?
                    source :
                    (
                        target.IsAssignableFrom(source) ?
                        target :
                        default
                    )
                );
            if(topLevelType != null)
            {
                foreach(var p in topLevelType.GetProperties())
                {
                    var sourceProperty = source.GetProperty(p.Name);
                    var targetProperty = target.GetProperty(p.Name);

                    if(sourceProperty.CanRead && targetProperty.CanWrite)
                    {
                        // 確認是否是基本型別
                        if (IsSimpleType(sourceProperty.PropertyType))
                        {
                            il.Emit(OpCodes.Nop);
                            il.Emit(OpCodes.Ldarg_1);
                            if (targetPropertyMethods != null)
                            {
                                foreach (var m in targetPropertyMethods)
                                {
                                    il.Emit(OpCodes.Callvirt, m);
                                }
                            }
                            il.Emit(OpCodes.Ldarg_0);
                            if (sourcePropertyMethods != null)
                            {
                                foreach (var m in sourcePropertyMethods)
                                {
                                    il.Emit(OpCodes.Callvirt, m);
                                }
                            }
                            il.Emit(OpCodes.Callvirt, sourceProperty.GetMethod);
                            il.Emit(OpCodes.Callvirt, targetProperty.SetMethod);
                        }
                        else
                        {
                            var label = il.DefineLabel();
                            il.Emit(OpCodes.Nop);
                            il.Emit(OpCodes.Ldarg_0);
                            if (sourcePropertyMethods != null)
                            {
                                foreach (var m in sourcePropertyMethods)
                                {
                                    il.Emit(OpCodes.Callvirt, m);
                                }
                            }
                            il.Emit(OpCodes.Callvirt, sourceProperty.GetMethod);
                            il.Emit(OpCodes.Ldnull);
                            il.Emit(OpCodes.Cgt_Un);
                            il.Emit(OpCodes.Brfalse_S, label);
                            il.Emit(OpCodes.Nop);
                            il.Emit(OpCodes.Ldarg_1);
                            if (targetPropertyMethods != null)
                            {
                                foreach (var m in targetPropertyMethods)
                                {
                                    il.Emit(OpCodes.Callvirt, m);
                                }
                            }
                            var constructor = targetProperty.PropertyType.GetConstructor(Type.EmptyTypes);
                            if (constructor == null)
                                il.Emit(OpCodes.Ldnull);
                            else
                                il.Emit(OpCodes.Newobj, constructor);
                            il.Emit(OpCodes.Callvirt, targetProperty.GetSetMethod());
                            if (constructor != null)
                            {
                                var sourcePropertyMethods2 = new List<MethodInfo>(sourcePropertyMethods ?? new List<MethodInfo>())
                                    {
                                        sourceProperty.GetGetMethod()
                                    };
                                var targetPropertyMethods2 = new List<MethodInfo>(targetPropertyMethods ?? new List<MethodInfo>())
                                    {
                                        targetProperty.GetGetMethod()
                                    };
                                il.Emit(OpCodes.Nop);
                                FastCloneProxyNested(il, sourceProperty.PropertyType, targetProperty.PropertyType, sourcePropertyMethods2, targetPropertyMethods2);
                            }
                            il.MarkLabel(label);
                        }
                    }
                }
            }
            else
                throw new InvalidOperationException("source 與 target 型別不存在階層關聯。");
        }
        
        private static bool IsSimpleType(Type type)
            => type.IsPrimitive ||
                new Type[] {
                    typeof(string),
                    typeof(decimal),
                    typeof(DateTime),
                    typeof(DateTimeOffset),
                    typeof(TimeSpan),
                    typeof(Guid)
                }.Contains(type) ||
                type.IsEnum ||
                (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) && IsSimpleType(type.GetGenericArguments()[0]));

上述語法為對應的複製語法的範例

以上程式碼仍有一些基本問題

  1. 使用 Brfalse_S 來跳到指定 label ,導致如果 property 太多或階層太長時,會發生問題,可以改成用 Brfalse 來跳轉,只是我的設想是簡單的設定 Model 的複製,所以是採用此操作代碼。
  2. 基本上某些特殊的物件可能無法正常複製,還有沒有考慮複製陣列物件這件事。





留言

這個網誌中的熱門文章

DB 資料庫呈現復原中

Outlook 刪除大量重覆信件

[VB.Net] If vs IIf ,兩者的差異