Документы для 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);
}
}
}