5

【类型转换】使用c#实现简易的类型转换(Emit,Expression,反射) - 四处观察

 8 months ago
source link: https://www.cnblogs.com/1996-Chinese-Chen/p/17932654.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

【类型转换】使用c#实现简易的类型转换(Emit,Expression,反射)

    哈喽。大家好,好久不见,最近遇到了一个场景,就是在FrameWork的asp.net mvc中,有个系统里面使用的是EntityFramework的框架,在这个框架里,提供了一个SqlQuery的方法,这个方法很好用啊,以至于在EFCORE8里面又添加了回来,不过不知道性能怎么样,我遇到的场景是通过SqlQuery查询的时候,转换很慢,我估计那背后大概率是使用反射造成的, 因为我的查询可能有上十万,甚至更多,就导致了这个转换的过程及其耗时,以至于刚开始我是想通过Emit等方式去实现一个高性能转换,可是到最后没有去弄,因为我用了DataCommand去查询,最后循环DataReader来实现硬赋值,这样性能是最好,一下减少了好多秒,提升了80%,但也给了我一个灵感,一个实现简易的类型转换的灵感,所以在上周我就把代码写了出来,不过由于工作的忙碌,今天才开始写博客,接下来就呈上。

    对了,有关EMIT和表达式树的知识,诸位可以看我之前的博客表达式树,IL。其中IL有两合集。

    众所周知,我们的c#代码在编译器编译,都会编译成IL代码,最后再去通过JIT转化为机器码,运行在系统中去的,所以IL代码的性能是比c#代码高的,同时,学习的成本,编写的成本也是机器高,我也是在自己感兴趣,瞎琢磨,就会了一丝丝皮毛,很多人说IL难写,其实,对于代码中的Opcodes那些,我只记一些常用的,对于剩下的,我都是在写的时候才去看文档,总之呢,要学的东西有很多,但掌握了学习的方法,才是持之以恒的手段。

    接下来,就呈上IL代码,分为两部分,一个是List转List,一个是实体转实体的。

    在这几个例子中,所有的前提都是实体的属性名称是一样的,如果需要扩展类型不一样,或者哪些不转换,从哪个属性转换到哪个属性,就需要各位自己去扩展了,本来我是想写这些的,,但是懒癌犯了,哈哈哈哈,需要各位看官自己动手了,以下代码,除了反射,其他的我都加了注释,反射大家都看得懂。

    在下面的第一个方法,我们定义了执行转换集合的方法,并返回了一个委托,我们在实际开发中,都可以返回委托,最终可以将方法缓存起来,这样在后续的时候直接调用,性能提升爆炸,因为你每次创建Emit方法的时候,耗时也会挺长的,在有时候,像哪些主流的Mapper,他们内部肯定都做了缓存。下面的集合转集合,大致的原理代码就是定义一个方法ConvertToType,返回类型是List<TR>,入参是List<T>,然后定义循环的开始结束变量,以及最终返回结果集,还有循环内部的时候,我们创建的变量,最终将这个变量添加到返回的结果集中,在往下就是拿入参集合的数量,定义循环开始和结束的label,在往下走就是,初始化返回值集合,赋值给本地的localRes变量,将0赋值给开始循环的变量,也就是for(int i=0),下面就是给结束的循环值赋值为入参集合的Count。

    下面的代码每行基本都有注释,所以我在这里也不做过多的讲解。

    集合和单个的区别就在于集合是多了一个循环的主体,其他都和单个是一样的,以及集合的代码块中,我没有添加try catch的代码块。

    internal class EmitExecute<T, TR> : IExecute<T, TR> where T : class where TR : class, new()
    {
        public Func<List<T>, List<TR>> ExecuteList()
        {
            var dynamicMethod = new DynamicMethod("ConvertToType", typeof(List<TR>), new Type[] { typeof(List<T>) });
            #region 变量定义
            var ilMethod = dynamicMethod.GetILGenerator();
            var localBegin = ilMethod.DeclareLocal(typeof(int));//定义循环开始变量
            var localEnd = ilMethod.DeclareLocal(typeof(int));//结束变量
            var localres = ilMethod.DeclareLocal(typeof(List<TR>));//返回值
            var localSignleRes = ilMethod.DeclareLocal(typeof(TR));//变量
            var countMethod = typeof(List<T>).GetProperty("Count").GetGetMethod();
            #endregion
            var labelXunhuan = ilMethod.DefineLabel();//定义循环主体
            var labelEnd = ilMethod.DefineLabel();//定义结束标签主体
            #region 实例化返回值
            ilMethod.Emit(OpCodes.Nop);
            ilMethod.Emit(OpCodes.Newobj, typeof(List<TR>).GetConstructor(Type.EmptyTypes));//初始化返回值变量
            ilMethod.Emit(OpCodes.Stloc, localres);//结果赋值给返回值变量变量
            #endregion
            #region 给循环的起始变量赋值 0是开始,count是获取的入参的集合的count
            ilMethod.Emit(OpCodes.Ldc_I4_0);//将0加载到栈上
            ilMethod.Emit(OpCodes.Stloc, localBegin);//给循环的开始值begin赋值0
            ilMethod.Emit(OpCodes.Ldarg_0);//加载入参
            ilMethod.Emit(OpCodes.Callvirt, countMethod);//将入参的count加载到栈上
            ilMethod.Emit(OpCodes.Stloc, localEnd);//给结束变量赋值为集合的count
            ilMethod.Emit(OpCodes.Br, labelXunhuan);//无条件跳转到循环的label,即开始循环
            #endregion
            #region 循环结束标签
            ilMethod.MarkLabel(labelEnd);//标记这段代码是labelend的代码

            ilMethod.Emit(OpCodes.Ldloc, localres);//加载返回值变量
            ilMethod.Emit(OpCodes.Ret);//返回
            #endregion
            #region 循环主体
            ilMethod.MarkLabel(labelXunhuan);//标记是labelbegin的代码
            ilMethod.Emit(OpCodes.Ldloc, localBegin);//从栈中加载开始变量
            ilMethod.Emit(OpCodes.Ldloc, localEnd);//从栈中加载结束变量
            ilMethod.Emit(OpCodes.Bge, labelEnd);//比较第0个是否小于等于第一个

            //ilMethod.Emit(OpCodes.Call, typeof(List<People>).GetMethod("Add"));
            ilMethod.Emit(OpCodes.Newobj, typeof(TR).GetConstructor(Type.EmptyTypes));//创建people的实例化
            ilMethod.Emit(OpCodes.Stloc, localSignleRes);//结果赋值people变量
            var properties = typeof(T).GetProperties().Where(s => typeof(TR).GetProperties().Any(a => a.Name == s.Name)).ToList();
            if (properties != null)
            {
                foreach (var item in properties)
                {
                    var getMethod = typeof(T).GetProperty(item.Name).GetGetMethod();//获取list【i】的species属性
                    var setMethod = typeof(TR).GetProperty(item.Name).GetSetMethod();
                    ilMethod.Emit(OpCodes.Ldloc, localSignleRes);//加载定义好的people
                    ilMethod.Emit(OpCodes.Ldarg_0);//加载入参
                    ilMethod.Emit(OpCodes.Ldloc, localBegin);//加载循环的开始变量
                    ilMethod.Emit(OpCodes.Callvirt, typeof(List<T>).GetMethod("get_Item"));// 调用 List<Animal>.get_Item 方法,将结果压入栈顶
                    ilMethod.Emit(OpCodes.Callvirt, getMethod);//拿到getitem的返回值的species属性
                    ilMethod.Emit(OpCodes.Call, setMethod);//给people的species属性赋值
                }
            }

            //ilMethod.Emit(OpCodes.Ldloc, localPeople);//加载定义好的people
            //ilMethod.Emit(OpCodes.Ldarg_0);//加载入参
            //ilMethod.Emit(OpCodes.Ldloc, localBegin);//加载循环的开始变量
            //ilMethod.Emit(OpCodes.Callvirt, typeof(List<T>).GetMethod("get_Item"));// 调用 List<Animal>.get_Item 方法,将结果压入栈顶
            //ilMethod.Emit(OpCodes.Callvirt, getAMethod);//拿到getitem的返回值的species属性
            //ilMethod.Emit(OpCodes.Call, property);//给people的species属性赋值

            ilMethod.Emit(OpCodes.Ldloc, localres);//加载返回值
            ilMethod.Emit(OpCodes.Ldloc, localSignleRes);//加载people

            ilMethod.Emit(OpCodes.Call, typeof(List<TR>).GetMethod("Add"));//将people添加道返回值
            ilMethod.Emit(OpCodes.Ldc_I4_1);//将1加载到栈上
            ilMethod.Emit(OpCodes.Ldloc, localBegin);//从栈中加载开始变量
            ilMethod.Emit(OpCodes.Add);//相加
            ilMethod.Emit(OpCodes.Stloc, localBegin);//结果赋值给本地0个局部变量
            ilMethod.Emit(OpCodes.Br, labelXunhuan);
            #endregion
            var func = dynamicMethod.CreateDelegate<Func<List<T>,List<TR>>>();
            return func;
        }

        public Func<T, TR> ExecuteSingle()
        {
            var dynamicMethod = new DynamicMethod("ConvertToType", typeof(TR), new Type[] { typeof(T) });
            var method = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });//输出字符串
            var ilMethod = dynamicMethod.GetILGenerator();
            var localRes = ilMethod.DeclareLocal(typeof(TR));//变量
            var ex = typeof(Exception);
            var localEx = ilMethod.DeclareLocal(ex);
            var str = typeof(Exception).GetMethod("ToString");
            ilMethod.Emit(OpCodes.Nop);
            ilMethod.Emit(OpCodes.Newobj, typeof(TR).GetConstructor(Type.EmptyTypes));//初始化返回值变量  new tr();
            ilMethod.Emit(OpCodes.Stloc, localRes);//结果赋值给返回值变量变量var tr=new tr();
            var properties = typeof(T).GetProperties().Where(s => typeof(TR).GetProperties().Any(a => a.Name == s.Name)).ToList();
            if (properties != null)
            {
                ilMethod.BeginExceptionBlock();//try
                foreach (var item in properties)
                {
                    var getMethod = typeof(T).GetProperty(item.Name).GetGetMethod();//获取list【i】的species属性
                    var setMethod = typeof(TR).GetProperty(item.Name).GetSetMethod();
                    ilMethod.Emit(OpCodes.Ldloc, localRes);//加载定义好的people
                    ilMethod.Emit(OpCodes.Ldarg_0);//加载入参
                    ilMethod.Emit(OpCodes.Callvirt, getMethod);//拿到getitem的返回值的species属性
                    ilMethod.Emit(OpCodes.Call, setMethod);//给people的species属性赋值
                }
                ilMethod.BeginCatchBlock(ex);
                ilMethod.Emit(OpCodes.Stloc, localEx);// 将异常的ex赋值给我们的本地变量ex
                ilMethod.Emit(OpCodes.Ldloc, localEx);//加载ex变量
                ilMethod.Emit(OpCodes.Callvirt, str);//调用tostring()
                ilMethod.Emit(OpCodes.Call, method);//调用console.writeline方法打印出异常信息
                ilMethod.Emit(OpCodes.Newobj, typeof(TR).GetConstructor(Type.EmptyTypes));
                ilMethod.Emit(OpCodes.Stloc, localRes);//结果赋值给返回值变量变量
                ilMethod.EndExceptionBlock();
            }
            ilMethod.Emit(OpCodes.Ldloc, localRes);//加载返回值
            ilMethod.Emit(OpCodes.Ret);
            var func = dynamicMethod.CreateDelegate<Func<T, TR>>();
            return func;
        }
    }
}

Expression

    接下来,是表达式树的实现方式,表达式树的其实和Emit的我感觉都差不多,不过和emit相比,肯定大家都喜欢写Expression,毕竟是c#代码,写起来比较舒适,在下面代码就是定义了入参的source,以及从source那指定索引的item,以及返回值res,异常的定义和异常的message,在下面就是循环两个公共属性的信息,调用bind方法,从item的里面拿出sourceproperty的属性和targetproperty绑定,然后给res初始化,设置他的count为source的count,并且判断如果source长度是0,就直接返回一个空的集合,下面有一个构造循环的方法,判断index是否小于集合的count,如果不成立,直接调用break标签,也就是我们的break关键字,如果成立,拿出对应的item,然后调用了MemberInit方法,初始化了一个TR,然后调用Add方法添加到返回的结果集合中,这样就实现了一个一个的转换,最后将所有的表达式整合为一个代码块,通过Block再加入Try Catch,最终编译成一个Func的委托。下面的单个的原理也是一样的。

  internal class ExpressionExecute<T, TR> : IExecute<T, TR> where T : class where TR : class, new()
  {
      private List<PropertyInfo> properties;
      public ExpressionExecute()
      {
          properties = typeof(TR).GetProperties().Where(s => typeof(T).GetProperties().Any(a => a.Name == s.Name)).ToList();//获取相同的属性名称
      }
      public Func<List<T>, List<TR>> ExecuteList()
      {            var sourceParam = Expression.Parameter(typeof(List<T>), "source");//定义入参
          var itemVar = Expression.Variable(typeof(T), "item");//定义从集合获取的item变量
          var resVar = Expression.Variable(typeof(List<TR>), "res");//定义返回的结果变量
          var ex = Expression.Variable(typeof(Exception), "ex");
          var msg = Expression.Property(ex, "Message");

          var method = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });
          var memberBindings = new List<MemberBinding>();//memberbind
          var listExpressions = new List<Expression>();//expression

          foreach (var property in properties)
          {
              var sourceProperty = typeof(T).GetProperty(property.Name);
              var targetProperty = typeof(TR).GetProperty(property.Name);

              if (sourceProperty.PropertyType == targetProperty.PropertyType)//判断类型
              {
                  memberBindings.Add(Expression.Bind(targetProperty, Expression.Property(itemVar, sourceProperty)));//相等直接bind add
              }
          }

          listExpressions.Add(Expression.Assign(resVar, Expression.New(typeof(List<TR>))));//设置res=new list<tr>
          listExpressions.Add(Expression.Assign(Expression.Property(resVar, "Capacity"), Expression.Property(sourceParam, "Count"))); //设置res.count==入参的count

          var indexVar = Expression.Variable(typeof(int), "index");//循环索引
          var breakLabel = Expression.Label("LoopBreak");//break标签

          listExpressions.Add(Expression.IfThen(
              Expression.Equal(Expression.Property(sourceParam, "Count"), Expression.Constant(0)),//如果source.cout==0
              Expression.Return(breakLabel, resVar, typeof(List<TR>)) //直接 break 返回res
          ));
          //构造循环
          var innerLoop = CreateLoop(indexVar, sourceParam, itemVar,resVar,memberBindings,breakLabel);
          listExpressions.AddRange(new Expression[] { innerLoop , Expression.Label(breakLabel) , resVar });
          var body = Expression.Block(new[] { itemVar, indexVar, resVar }, listExpressions);//整合一个代码块
          var tryCatch = Expression.TryCatch(body, new CatchBlock[] { Expression.Catch(ex, Expression.Block(Expression.Call(null, method, msg), Expression.New(typeof(List<TR>)))) });//try catch
          var lambda = Expression.Lambda<Func<List<T>, List<TR>>>(tryCatch, sourceParam);
          return lambda.Compile();
      }
      private LoopExpression CreateLoop(ParameterExpression indexVar,ParameterExpression sourceParam,ParameterExpression itemVar,ParameterExpression resVar,List<MemberBinding> memberBindings,LabelTarget breakLabel)
      {
          var addMethod = typeof(List<TR>).GetMethod("Add");//结果集的add方法
        return  Expression.Loop(
              Expression.IfThenElse(//如果index <count 进入block
                  Expression.LessThan(indexVar, Expression.Property(sourceParam, "Count")),
                  Expression.Block(
                      Expression.Assign(itemVar, Expression.Property(sourceParam, "Item", indexVar)),//设置item=source【index】
                      Expression.Call(resVar, addMethod, Expression.MemberInit(Expression.New(typeof(TR)), memberBindings)),//调用res.add方法
                      Expression.PostIncrementAssign(indexVar)//index=index+1
                  ),
                  Expression.Break(breakLabel)//如果index<count不成立,直接中断循环
              )
          );
      }
      public Func<T, TR> ExecuteSingle()
      {
          var express = Expression.Parameter(typeof(T), "source");
          var memberBindings = new List<MemberBinding>();//memberbing
          var list = new List<Expression>();
          var action = new Func<string, string>(s => {
              return typeof(TR).GetProperty(s).Name;
          });//根据属性名称获取属性,由于property第二个参数必须 string or method,下方就只有他通过call的方式获取属性的name名称
          int ipropertydx = 0;//相同属性遍历所以
          memberBindings.Clear();

          var peoples = Expression.New(typeof(TR));//创建新的tr实例
          foreach (var item in properties)//属性遍历
          {
              var property = Expression.Property(peoples, item.Name);//获取tr实例属性
              var memberInfo = typeof(TR).GetMember(item.Name).FirstOrDefault(); // 获取 MemberInfo 对象
              if (memberInfo != null)
              {
                  var assignment = Expression.Bind(//将属性和source[index].属性关联起来
                      memberInfo, Expression.Property(      //获取source的属性
                          express,
                              //调用上面的委托返回要拿的属性名称也就是A.name=B.name
                              Expression.Lambda<Func<string>>(
                              Expression.Call(Expression.Constant(action.Target), action.Method, //调用action拿名称
                              Expression.Property(
                                  Expression.ArrayIndex(Expression.Constant(properties.ToArray()), Expression.Constant(ipropertydx)), "Name")//获取properties的第ipropertydx的name名称
                              )
                              ).Compile()  //编译为func《string》委托
                              ()
                              ));
                  memberBindings.Add(assignment);//将每个people的每个属性赋值的assignment添加进去
                  ipropertydx++;
              }
          }
          var memberInit = Expression.MemberInit(peoples, memberBindings);//将peoples初始化  初始化绑定的每一个成员
          var func = Expression.Lambda<Func<T, TR>>(memberInit, express).Compile();//编译为委托
          return func;
      }
  }

    反正,反射是很耗时的,少量情况还好,大量并不建议使用,虽然很好用,这里我也只是做一个例子,让我自己用,肯定优选前面两个,这个代码更不用讲了,懂得都懂,

  internal class ReflectionExecute<T, TR> : IExecute<T, TR> where T : class where TR : class,new()
  {
      public Func<List<T>, List<TR>> ExecuteList()
      {
          var res = new Func<List<T>, List<TR>>(s =>
          {
              var resList = new List<TR>();
              var properties = typeof(T).GetProperties().Where(s => typeof(TR).GetProperties().Any(a => a.Name == s.Name)).ToList();//获取相同的属性名称
              foreach (var item in s)
              {
                  var tr = new TR();
                  foreach (var itemproperty in properties)
                  {
                      var val = itemproperty.GetValue(item, null);
                      if (val != null)
                      {
                          var setProperty = typeof(TR).GetProperty(itemproperty.Name);
                          setProperty.SetValue(tr, val);
                      }
                  }
                  resList.Add(tr);
              }
              return resList;
          });
          return res;
      }

      public Func<T, TR> ExecuteSingle()
      {
          var res = new Func<T, TR>(s =>
          {

              var properties = typeof(TR).GetProperties().Where(s => typeof(T).GetProperties().Any(a => a.Name == s.Name)).ToList();//获取相同的属性名称
              var tr = new TR();
              foreach (var itemproperty in properties)
              {
                  var val = itemproperty.GetValue(s, new object[] { });
                  if (val != null)
                  {
                      var setProperty = typeof(TR).GetProperty(itemproperty.Name);
                      setProperty.SetValue(tr, val);
                  }
              }
              return tr;
          });
          return res;
      }
  }

    这玩意,我自己简单测试了一下,本想用benmark简单跑一下,但是麻烦,就代码自己测试了一下,在第一次构建表达式树的方法,会有些许耗时,但是在最后如果有缓存方法,那性能不必Emit差,总之,性能方面优选Emit和表达式树,反射就不做考虑。

    赶鸭子上架,水了一篇博客,如有疑问,可以随时呼我,QQ934550201.

    代码地址:

    链接:https://pan.baidu.com/s/1vW9LPfYHmvk6Y08qEYA9sw
    提取码:vj34


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK