Emit, DynamicMethod 和 Field 复制
项目需求,有的时候需要从继承类向基类转换,有人说了,不是可以直接用吗?都不用转换的。是的,在很多情况下是这样的,但是有的时候作为Abstract的类中需要为多个子类写方法的时候就没有这么简单了。或者参数是接口,需要转成实体之类的需求,还是在某些场合下要碰到的。
原来我用的是对所有属性同步,后来发现这样的效果不彻底,有的字段没有被属性公开,从而会影响该类的一些外在表现形式。上篇里也说了,类的数据都是保存在字段里的,一不做二不休,我们干脆做Field的复制得了。
直接用反射非常简单,代码也很短。我们先看看。
public static TTarget ConvertToByFields<TTarget>(object src) where TTarget : class, new() { var srcType = src.GetType(); var targetType = typeof(TTarget); if (srcType == targetType) return src as TTarget; if (src == null) return default(TTarget); var srcFields = srcType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); var targetFields = targetType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); var commonFields = srcFields.Where(f => targetFields.Any(tf => tf.Name == f.Name && tf.FieldType == f.FieldType) && !f.IsInitOnly && !f.IsLiteral).ToList(); var target = Activator.CreateInstance<TTarget>(); commonFields.ForEach(f => f.SetValue(target, f.GetValue(src))); return target; }
但是高手们都说反射的效率低得很,所以我们还是参照下高手的意见,看能不能将之改成效率较高的Emit或者Expression的方式看看。考虑下情况:Expression只支持单条语句,所以对多个字段操作肯定是不行的,除非给每个FieldInfo都定义一个Lambda,这样比较痛苦,而且想来的话肯定不如一个DynamicMethod有效率,那么我们就用DynamicMethod来实现吧!~
首先我们的想法是建立一个方法,这个方法接受一个object类型的src参数输入,然后在方法里实例化一个TargetType的对象,将src所有字段的值都赋给这个target在返回。嗯,这就是我们的目的。
看代码:
//委托的字典,用来缓存。 static Dictionary<string, Delegate> _genericDelegates = new Dictionary<string, Delegate>(); public static TTarget ConvertToByFields<TTarget>(object src, bool isClone) where TTarget : class, new() { var srcType = src.GetType(); var targetType = typeof(TTarget); //检查是否有必要进行操作。 if (!isClone && srcType == targetType) return src as TTarget; if (src == null) return default(TTarget); //生成唯一的KEY var key = "CopyFieldsFrom_" + srcType.FullName + "_To_" + targetType.FullName; #region Emit if (!_delegates.ContainsKey(key)) { //找出公用的字段 var srcFields = srcType.GetAllFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); var targetFields = targetType.GetAllFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); var commonFields = srcFields.Where(f => targetFields.Any(tf => tf.Name == f.Name && tf.FieldType == f.FieldType) && !f.IsInitOnly && !f.IsLiteral).ToList(); //创建DynamicMethod,注意SkipVisiblity一定要设置成True. var dm = new DynamicMethod(key, MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, targetType, new Type[] { typeof(object) }, srcType, true); var il = dm.GetILGenerator(); il.DeclareLocal(srcType); il.DeclareLocal(targetType); il.DeclareLocal(targetType); il.Emit(OpCodes.Nop); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Castclass, srcType); il.Emit(OpCodes.Stloc_0); il.Emit(OpCodes.Newobj, targetType.GetConstructor(new Type[] { })); il.Emit(OpCodes.Stloc_1); commonFields.ForEach(f => { il.Emit(OpCodes.Ldloc_1); il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ldfld, f); il.Emit(OpCodes.Stfld, targetFields.First(tf => tf.Name == f.Name)); }); il.Emit(OpCodes.Ldloc_1); il.Emit(OpCodes.Stloc_2); il.Emit(OpCodes.Ldloc_2); il.Emit(OpCodes.Ret); var lambda = (Func<object, TTarget>)dm.CreateDelegate(typeof(Func<object, TTarget>)); _delegates[key] = lambda; } #endregion return ((Func<object, TTarget>)_delegates[key])(src); }
上面的代码也没有啥技术含量,偶是对这Reflectori写的~ 其实超简单,无非主要的就是
commonFields.ForEach(f => { il.Emit(OpCodes.Ldloc_1); il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ldfld, f); il.Emit(OpCodes.Stfld, targetFields.First(tf => tf.Name == f.Name)); });
说白了,就是GetValue,然后SetValue。呵呵。还有CommonFields的过滤也要注意Readonly和常量。
唯一值得一说的就是创建DynamicMethod的时候的参数skpVisiblity参数,一定要设置成True,不然会报FieldAccessException的,因为Field都是Private的嘛! :-D
--------------------------------------------
更新:因为GetFields值返回本Type的字段而不会返回上级的字段,所以需要这个扩展方法:
private static IEnumerable<FieldInfo> GetAllFields(this Type type, BindingFlags flags) { var fields = new Dictionary<string, FieldInfo>(); var typeNow = type; do { var fs = typeNow.GetFields(flags).ToList(); fs.ForEach(f => { if (!fields.ContainsKey(f.Name + "!" + f.FieldType.FullName)) fields.Add(f.Name + "!" + f.FieldType.FullName, f); }); typeNow = typeNow.BaseType; } while (typeNow != null); return fields.Values; }
posted on 2010-01-11 14:39 NullReference 阅读(950) 评论(0) 编辑 收藏 举报