<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      方法的三種調用形式

      在《可以調用Null的實例方法嗎?》一文中,我談到.NET方法的三種調用形式,現在我們就來著重聊聊這個話題。具體來說,這里所謂的三種方法調用形式對應著三種IL指令:Call、CallVirt和Calli。

      一、三個方法調用指令
      二、三種方法調用形式
      三、虛方法的分發(fā)(virtual dispatch)
      四、性能差異

      一、三個方法調用指令

      雖然C#的方法具有靜態(tài)方法和實例方法之分,但是在IL層面,它們之間并沒有什么不同,就是單純的“函數”而已,而且這個函數的第一個參數的類型永遠是方法所在的類型。所以在IL層面,方法總是“靜態(tài)”的,調用實例方法的本質就是將目標實例作為第一個參數,對于靜態(tài)方法,第一個參數永遠是Null/Default(值類型)。我在《實例方法和靜態(tài)方法有區(qū)別嗎?》中曾經著重談到過這個問題。

      Call和CallVirt指令執(zhí)行方法的流程只有兩步:將所有參數壓入棧中 + 執(zhí)行方法。它們之間的不同之處在于:Call指令編譯時就已經確定了執(zhí)行的方法,而CallVirt則是在運行時根據作為第一個參數的實例類型決定最終執(zhí)行的方法。Calli指令則有所不同,我們執(zhí)行該指令時需要指定目標方法的指針,整個流程包括三步:將所有參數壓入棧中 + 將目標方法指針壓入棧中+執(zhí)行方法。

      二、三種方法調用形式

      接下來我們使用動態(tài)方法的形式演示上述三種方法調用指令的使用。具體來說,我們采用三種方式調用定義在Calculator中用來進行加法運算的Add方法,為此我們利用CreateInvoker方法根據指定的指令生成一個對應的Func<Calculator, int, int, int>委托。在CreateInvoker方法中,我們創(chuàng)建一個與Func<Calculator, int, int, int>委托匹配的動態(tài)方法。在IL Emit過程中,我們先將三個參數(Calculator對象和Add方法的參數a和b)壓入棧中。如果指定的是Call和CallVirt指令,我們直接執(zhí)行它們就可以了。如果指定的是Calli指令,我們得執(zhí)行Ldftn指令將Add方法的指針壓入棧中(方法指針通過指定的MethodInfo對象提供),然后再執(zhí)行Calli指令。

      var calculator = new Calculator();
      
      var invoker = CreateInvoker(OpCodes.Call);
      Console.WriteLine($"1 + 2 = {invoker(calculator, 1, 2)} [Call]");
      
      invoker = CreateInvoker(OpCodes.Callvirt);
      Console.WriteLine($"1 + 2 = {invoker(calculator, 1, 2)} [Callvirt]");
      
      invoker = CreateInvoker(OpCodes.Calli);
      Console.WriteLine($"1 + 2 = {invoker(calculator, 1, 2)} [Calli]");
      
      static Func<Calculator, int, int, int> CreateInvoker(OpCode opcode)
      {
          var method = typeof(Calculator).GetMethod("Add")!;
          var dynamicMethod = new DynamicMethod("Add", typeof(int), [typeof(Calculator), typeof(int), typeof(int)]);
          var il = dynamicMethod.GetILGenerator();
          il.Emit(OpCodes.Ldarg_0);
          il.Emit(OpCodes.Ldarg_1);
          il.Emit(OpCodes.Ldarg_2);
      
          if (opcode == OpCodes.Call)
          {
              il.Emit(OpCodes.Call, method);
          }
          else if (opcode == OpCodes.Callvirt)
          {
              il.Emit(OpCodes.Callvirt, method);
          }
          else if (opcode == OpCodes.Calli)
          {
              il.Emit(OpCodes.Ldftn, method);
              il.EmitCalli(OpCodes.Calli, CallingConvention.ThisCall, typeof(int), [typeof(Calculator), typeof(int), typeof(int)]);
          }
      
          il.Emit(OpCodes.Ret);
          return (Func<Calculator, int, int, int>)dynamicMethod.CreateDelegate(typeof(Func<Calculator, int, int, int>));
      }
      
      public class Calculator
      {
          public virtual int Add(int a, int b) => a + b;
      }

      演示程序利用指定的三種方法指令創(chuàng)建了對應的Func<Calculator, int, int, int>,然后指定相同的參數(Calculator實例、整數1、2)執(zhí)行它們,我們最終會在控制臺上得到如下的輸出結果。

      image

      三、虛方法的分發(fā)(virtual dispatch)

      雖然Calculator的Add是個虛方法,由于Call指令執(zhí)行的目標方法在編譯時就確定,Calli則是我們以指針的形式指定了執(zhí)行的方法,不論我們指定的目標對象具體是何類型,執(zhí)行的永遠是定義在Calculator類型的那個Add方法。面向對象“多態(tài)”的能力只能通過CallVirt指令來實現。

      var calculator = new FakeCalculator();
      
      var invoker = CreateInvoker(OpCodes.Call);
      Console.WriteLine($"1 + 2 = {invoker(calculator, 1, 2)} [Call]");
      
      invoker = CreateInvoker(OpCodes.Callvirt);
      Console.WriteLine($"1 + 2 = {invoker(calculator, 1, 2)} [Callvirt]");
      
      invoker = CreateInvoker(OpCodes.Calli);
      Console.WriteLine($"1 + 2 = {invoker(calculator, 1, 2)} [Calli]");
      
      
      public class FakeCalculator : Calculator
      {
          public override int Add(int a, int b) => a - b;
      }

      以如上的程序為例,我們定義了Calculator的派生類FakeCalculator,在重寫的Add方法中執(zhí)行“減法運算”。我們將這個FakeCalculator對象作為參數調用三個委托,會得到如下所示的輸出結果,可以看出CallVirt指令才能得到我們希望的結果。
      image

      當我們在使用Calli指令時, 由于我們是利用Ldfnt指令指定的定義在Calculator類型中的Add方法,所以起不到“多態(tài)”的作用。如果要實現多態(tài),我們得按照如下得方式使用Ldvirtfnt,該指令會從提取目標對象(通過執(zhí)行Ldarg_0指令壓入棧中)并解析出對應得方法。

      static Func<Calculator, int, int, int> CreateInvoker(OpCode opcode)
      {
          var method = typeof(Calculator).GetMethod("Add")!;
          var dynamicMethod = new DynamicMethod("Add", typeof(int), [typeof(Calculator), typeof(int), typeof(int)]);
          var il = dynamicMethod.GetILGenerator();
          il.Emit(OpCodes.Ldarg_0);
          il.Emit(OpCodes.Ldarg_1);
          il.Emit(OpCodes.Ldarg_2);
      
          if (opcode == OpCodes.Call)
          {
              il.Emit(OpCodes.Call, method);
          }
          else if (opcode == OpCodes.Callvirt)
          {
              il.Emit(OpCodes.Callvirt, method);
          }
          else if (opcode == OpCodes.Calli)
          {
              il.Emit(OpCodes.Ldarg_0);
              il.Emit(OpCodes.Ldvirtftn, method);
              il.EmitCalli(OpCodes.Calli, CallingConvention.ThisCall, typeof(int), [typeof(Calculator), typeof(int), typeof(int)]);
          }
      
          il.Emit(OpCodes.Ret);
          return (Func<Calculator, int, int, int>)dynamicMethod.CreateDelegate(typeof(Func<Calculator, int, int, int>));
      }

      再次執(zhí)行演示程序,就會得到我們希望得結果:

      image

      四、性能差異

      既然Call、CallVirt和Calli都是能幫助我們完成方法的執(zhí)行,我們自然會進一步關系它們的性能差異了,為此我們來做一個簡單的性能測試。

      BenchmarkRunner.Run<Test>();
      
      public class Test
      {
          private static readonly Func<Calculator, int, int, int> _call = CreateInvoker(OpCodes.Call);
          private static readonly Func<Calculator, int, int, int> _callvirt = CreateInvoker(OpCodes.Callvirt);
          private static readonly Func<Calculator, int, int, int> _calli = CreateInvoker(OpCodes.Calli);
          private static readonly Calculator _calculator = new FakeCalculator();
      
          [Benchmark]
          public int Call() => _call(_calculator, 1, 2);
      
          [Benchmark]
          public int Callvirt() => _callvirt(_calculator, 1, 2);
      
          [Benchmark]
          public int Calli() => _calli(_calculator, 1, 2);
      }

      如上所示的測試程序很簡單,我們調用CreateInvoker方法將針對三種指令的Func<Calculator, int, int, int>委托和目標對象FakeCalculator創(chuàng)建出來,并在三個Benchmark方法中執(zhí)行它們。從如下的測試結果可以看出,Call由于不需要進行”虛方法分發(fā)(Virtual Dispatch)”性能會比Callvirt執(zhí)行好一些,但總體來說差別不大,但是Calli指令調用方法的性能會差很多。
      image

      posted @ 2024-08-20 08:46  Artech  閱讀(2957)  評論(21)    收藏  舉報
      主站蜘蛛池模板: 成人免费区一区二区三区| 国产精品午夜av福利| 日本高清在线播放一区二区三区| 成人免费在线播放av| 一本无码在线观看| 强插少妇视频一区二区三区| 国产精品视频中文字幕| 无码福利写真片视频在线播放 | 国产天美传媒性色av高清| 国产精品久久久久7777| 99久久精品费精品国产一区二区 | 国产精品久久久久久无毒不卡 | 国内精品久久人妻互换| 女同另类激情在线三区| 国产综合视频一区二区三区 | 无套内射极品少妇chinese| 国产玖玖玖玖精品电影| 日本一本无道码日韩精品| 国产无遮挡无码视频在线观看| 无码人妻精品一区二区三区蜜桃 | 国产一精品一av一免费爽爽| 最新国产AV最新国产在钱| 黑人大群体交免费视频| 久久综合给合久久狠狠狠88| 精品人妻午夜福利一区二区| 久久久久综合一本久道| 久女女热精品视频在线观看| 国产一级r片内射免费视频| 欧美孕妇乳喷奶水在线观看| 盐边县| 午夜一区欧美二区高清三区| 成人做受视频试看60秒| 人妻系列无码专区久久五月天| 国产成人无码av一区二区| 亚洲精品日韩在线丰满| 新宾| 国产情侣激情在线对白| 国产精品人妻久久无码不卡| 国产亚洲精品AA片在线播放天 | 狠狠色狠狠色综合久久蜜芽| 久久精品亚洲中文字幕无码网站|