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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/932699/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 03:37:13  来源:igfitidea点击:

What is the difference between calling a delegate directly, using DynamicInvoke, and using DynamicInvokeImpl?

c#delegates

提问by Jason Baker

The docs for both DynamicInvoke and DynamicInvokeImpl say:

DynamicInvoke 和 DynamicInvokeImpl 的文档说:

Dynamically invokes (late-bound) the method represented by the current delegate.

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

I notice that DynamicInvoke and DynamicInvokeImpl take an array of objects instead of a specific list of arguments (which is the late-bound part I'm guessing). But is that the only difference? And what is the difference between DynamicInvoke and DynamicInvokeImpl.

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

回答by JaredPar

Really there is no functional difference between the two. if you pull up the implementation in reflector, you'll notice that DynamicInvoke just calls DynamicInvokeImpl with the same set of arguments. No extra validation is done and it's a non-virtual method so there is no chance for it's behavior to be changed by a derived class. DynamicInvokeImpl is a virtual method where all of the actual work is done.

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

回答by Marc Gravell

The main difference between calling it directly (which is short-hand for Invoke(...)) and using DynamicInvokeis performance; a factor of more than *700 by my measure (below).

直接调用它(它是 的简写Invoke(...))和使用之间的主要区别在于DynamicInvoke性能;根据我的测量(如下),一个超过 *700 的因素。

With the direct/Invokeapproach, the arguments are already pre-validated via the method signature, and the code already exists to pass those into the method directly (I would say "as IL", but I seem to recall that the runtime provides this directly, withoutany IL). With DynamicInvokeit needs to check them from the array via reflection (i.e. are they all appropriate for this call; do they need unboxing, etc); this is slow(if you are using it in a tight loop), and should be avoided where possible.

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

Example; results first (I increased the LOOPcount from the previous edit, to give a sensible comparison):

例子; 首先是结果(我增加了LOOP之前编辑的计数,以进行合理的比较):

Direct: 53ms
Invoke: 53ms
DynamicInvoke (re-use args): 37728ms
DynamicInvoke (per-cal args): 39911ms

With code:

用代码:

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");
}

回答by Rainer Hilmer

Coincidentally I have found another difference.

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

If Invokethrows an exception it can be caught by the expected exception type. However DynamicInvokethrows a TargetInvokationException. Here is a small demo:

如果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
   }
}


While the second test goes green, the first one faces a TargetInvokationException.

当第二个测试变为绿色时,第一个测试面临 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);
      }
   }
}