使用 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 的複製,所以是採用此操作代碼。
- 基本上某些特殊的物件可能無法正常複製,還有沒有考慮複製陣列物件這件事。
留言
張貼留言