NullReference

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.
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  阅读(949)  评论(0编辑  收藏  举报