使用 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]));
上述語法為對應的複製語法的範例
以上程式碼仍有一些基本問題
- 使用 Brfalse_S 來跳到指定 label ,導致如果 property 太多或階層太長時,會發生問題,可以改成用 Brfalse 來跳轉,只是我的設想是簡單的設定 Model 的複製,所以是採用此操作代碼。
- 基本上某些特殊的物件可能無法正常複製,還有沒有考慮複製陣列物件這件事。
留言
張貼留言