使用DynamicInvoke直接调用委托和使用DynamicInvokeImpl有什么区别?

Jas*_*ker 27 c# delegates

DynamicInvoke和DynamicInvokeImpl的文档都说:

动态调用(后期绑定)当前委托表示的方法.

我注意到DynamicInvoke和DynamicInvokeImpl采用了一个对象数组而不是一个特定的参数列表(这是我猜的后期绑定部分).但这是唯一的区别吗?DynamicInvoke和DynamicInvokeImpl之间有什么区别.

Mar*_*ell 33

直接调用(简称Invoke(...))和使用它的主要区别在于DynamicInvoke性能; 我的测量值(下图)超过*700的因子.

使用direct/Invokeapproach,参数已经通过方法签名预先验证,并且代码已经存在以直接将它们传递给方法(我会说"作为IL",但我似乎记得运行时直接提供了这个,没有任何IL).有了DynamicInvoke它需要通过反射从阵列检查他们(即是它们都适用于这个呼叫;他们需要拆箱等); 这很(如果你在紧密循环中使用它),应该尽可能避免.

例; 结果第一(我LOOP从上一次编辑增加了计数,给出了明智的比较):

Direct: 53ms
Invoke: 53ms
DynamicInvoke (re-use args): 37728ms
DynamicInvoke (per-cal args): 39911ms
Run Code Online (Sandbox Code Playgroud)

使用代码:

static void DoesNothing(int a, string b, float? c) { }
static void Main() {
    Action<int, string, float?> method = DoesNothing;

    int a = 23;
    string b = "abc";
    float? c = null;
    const int LOOP = 5000000;

    Stopwatch watch = Stopwatch.StartNew();
    for (int i = 0; i < LOOP; i++) {
        method(a, b, c);
    }
    watch.Stop();
    Console.WriteLine("Direct: " + watch.ElapsedMilliseconds + "ms");

    watch = Stopwatch.StartNew();
    for (int i = 0; i < LOOP; i++) {
        method.Invoke(a, b, c);
    }
    watch.Stop();
    Console.WriteLine("Invoke: " + watch.ElapsedMilliseconds + "ms");

    object[] args = new object[] { a, b, c };
    watch = Stopwatch.StartNew();
    for (int i = 0; i < LOOP; i++) {
        method.DynamicInvoke(args);
    }
    watch.Stop();
    Console.WriteLine("DynamicInvoke (re-use args): "
         + watch.ElapsedMilliseconds + "ms");

    watch = Stopwatch.StartNew();
    for (int i = 0; i < LOOP; i++) {
        method.DynamicInvoke(a,b,c);
    }
    watch.Stop();
    Console.WriteLine("DynamicInvoke (per-cal args): "
         + watch.ElapsedMilliseconds + "ms");
}
Run Code Online (Sandbox Code Playgroud)


小智 10

巧合的是,我发现了另一个不同之处.

如果Invoke抛出异常,它可以被预期的异常类型捕获.然而DynamicInvoke抛出一个TargetInvokationException.这是一个小型演示:

using System;
using System.Collections.Generic;

namespace DynamicInvokeVsInvoke
{
   public class StrategiesProvider
   {
      private readonly Dictionary<StrategyTypes, Action> strategies;

      public StrategiesProvider()
      {
         strategies = new Dictionary<StrategyTypes, Action>
                      {
                         {StrategyTypes.NoWay, () => { throw new NotSupportedException(); }}
                         // more strategies...
                      };
      }

      public void CallStrategyWithDynamicInvoke(StrategyTypes strategyType)
      {
         strategies[strategyType].DynamicInvoke();
      }

      public void CallStrategyWithInvoke(StrategyTypes strategyType)
      {
         strategies[strategyType].Invoke();
      }
   }

   public enum StrategyTypes
   {
      NoWay = 0,
      ThisWay,
      ThatWay
   }
}
Run Code Online (Sandbox Code Playgroud)

当第二个测试变为绿色时,第一个测试面临TargetInvokationException.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SharpTestsEx;

namespace DynamicInvokeVsInvoke.Tests
{
   [TestClass]
   public class DynamicInvokeVsInvokeTests
   {
      [TestMethod]
      public void Call_strategy_with_dynamic_invoke_can_be_catched()
      {
         bool catched = false;
         try
         {
            new StrategiesProvider().CallStrategyWithDynamicInvoke(StrategyTypes.NoWay);
         }
         catch(NotSupportedException exc)
         {
            /* Fails because the NotSupportedException is wrapped
             * inside a TargetInvokationException! */
            catched = true;
         }
         catched.Should().Be(true);
      }

      [TestMethod]
      public void Call_strategy_with_invoke_can_be_catched()
      {
         bool catched = false;
         try
         {
            new StrategiesProvider().CallStrategyWithInvoke(StrategyTypes.NoWay);
         }
         catch(NotSupportedException exc)
         {
            catched = true;
         }
         catched.Should().Be(true);
      }
   }
}
Run Code Online (Sandbox Code Playgroud)


Jar*_*Par 9

真的,两者之间没有功能差异.如果你在反射器中提取实现,你会注意到DynamicInvoke只是使用相同的参数集调用DynamicInvokeImpl.没有进行额外的验证,它是一个非虚方法,所以它的行为不可能被派生类改变.DynamicInvokeImpl是一个虚拟方法,可以完成所有实际工作.