Разница между Invoke и DynamicInvoke

128

В чем разница между Invoke и DynamicInvoke в делегатах? Пожалуйста, дайте мне пример кода, который объясняет разницу между этими двумя методами.

testCoder
источник

Ответы:

206

Если у вас есть экземпляр делегата, вы можете знать точный тип или просто знать, что это Delegate. Если вы знаете точный тип, вы можете использовать Invoke, что очень быстро - все уже предварительно проверено. Например:

Func<int,int> twice = x => x * 2;
int i = 3;
int j = twice.Invoke(i);
// or just:
int j = twice(i);

Тем не мение! Если вы просто знаете, что это так Delegate, он должен разрешить параметры и т. Д. Вручную - это может включать распаковку и т. Д. - происходит много размышлений. Например:

Delegate slowTwice = twice; // this is still the same delegate instance
object[] args = { i };
object result = slowTwice.DynamicInvoke(args);

Обратите внимание: я написал argsдлинную руку, чтобы прояснить, что object[]задействован. Здесь много дополнительных затрат:

  • массив
  • проверка того, что переданные аргументы "подходят" для фактического MethodInfo
  • распаковка и т. д. по мере необходимости
  • отражательного Invoke
  • тогда вызывающему абоненту нужно что-то сделать для обработки возвращаемого значения

По сути, избегайте, DynamicInvokeкогда можете. Invokeвсегда предпочтительнее, если все, что у вас есть, - это a Delegateи an object[].

Для сравнения производительности в режиме выпуска вне отладчика (консольный exe) печатается следующее:

Invoke: 19ms
DynamicInvoke: 3813ms

Код:

Func<int,int> twice = x => x * 2;
const int LOOP = 5000000; // 5M
var watch = Stopwatch.StartNew();
for (int i = 0; i < LOOP; i++)
{
    twice.Invoke(3);
}
watch.Stop();
Console.WriteLine("Invoke: {0}ms", watch.ElapsedMilliseconds);
watch = Stopwatch.StartNew();
for (int i = 0; i < LOOP; i++)
{
    twice.DynamicInvoke(3);
}
watch.Stop();
Console.WriteLine("DynamicInvoke: {0}ms", watch.ElapsedMilliseconds);
Марк Гравелл
источник
3
Означает ли это, что в случае использования компилятор DynamicInvoke производит больше кода IL для обработки вызова делегата?
testCoder
2
@testCoder нет, он будет использовать отражение
Марк Грейвелл
@MarcGravell, когда я пробую это в методе, который вызывает событие, я получаю, что первый вызов метода занимает около 0,7766 мс, а второй - около 0,0568 мс. Когда первым является Invoke, он занимает больше времени, чем DynamicInvoke, или наоборот. Когда я попробовал ваш пример с 1 циклом и посмотрел на ms Invoke: 0,0478ms, DynamicInvoke: 0,053ms. Почему вы сравниваете их больше 1 звонка? И почему первый вызов функции занимает больше времени, чем второй?
uzay95
4
@ uzay95 Первый вызов метода вызывает выполнение JIT-компиляцией CLR - это применимо к любому методу при первом его вызове после запуска процесса. В таком сценарии вы можете сделать одно из трех: (1) запустить метод несколько раз, чтобы время, затраченное на первый вызов, стало незначительным в конечном результате, (2) не начинайте измерения, пока вы не Вызывали метод один раз или (3) использовали ngen.exe (излишний). Этот пост объясняет это достаточно хорошо ... stackoverflow.com/questions/4446203/…
Quanta
@ marc-gravell Вам не нужно создавать массив для передачи в DynamicInvoke, так как в сигнатуре метода указано ключевое слово params для параметра args.
zodo