В чем разница между вызовом делегата напрямую, использованием DynamicInvoke и использованием DynamicInvokeImpl?

Документы для DynamicInvoke и DynamicInvokeImpl говорят:

Динамически вызывает (позднюю) метод, представленный текущим делегировать.

Я замечаю, что DynamicInvoke и DynamicInvokeImpl берут массив объектов вместо определенного списка аргументов (который является частью последней части, которую я предполагаю). Но разве это единственное различие? И в чем разница между DynamicInvoke и DynamicInvokeImpl.

Ответ 1

Основное различие между прямым вызовом (которое является короткодействующим для Invoke(...)) и использованием DynamicInvoke - производительность; фактор более чем на 700 по моей мере (ниже).

При прямом / Invoke подходе аргументы уже предварительно проверяются с помощью сигнатуры метода, и код уже существует, чтобы передать их в метод напрямую (я бы сказал "как IL", но, похоже, я вспоминаю что среда выполнения обеспечивает это напрямую, без какого-либо ИЛ). С помощью DynamicInvoke необходимо проверить их из массива с помощью отражения (т.е. Все они подходят для этого вызова, им нужно распаковать и т.д.); это медленный (если вы используете его в узком цикле), и его следует избегать, когда это возможно.

Пример; (я увеличил счетчик LOOP от предыдущего редактирования, чтобы дать разумное сравнение):

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

С кодом:

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

Ответ 2

В действительности нет функциональной разницы между ними. если вы потянете реализацию в рефлекторе, вы заметите, что DynamicInvoke просто вызывает DynamicInvokeImpl с тем же набором аргументов. Никакая дополнительная проверка не выполняется, и это не виртуальный метод, поэтому нет возможности изменить его поведение производным классом. DynamicInvokeImpl - это виртуальный метод, в котором выполняется вся работа.

Ответ 3

Кстати, я нашел другое различие.

Если Invoke выдает исключение, оно может быть захвачено ожидаемым типом исключения. Однако DynamicInvoke выбрасывает a 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
   }
}

В то время как второй тест становится зеленым, первый сталкивается с объектом 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);
      }
   }
}