Différence entre Invoke et DynamicInvoke

128

Quelle est la différence entre Invoke et DynamicInvoke dans les délégués? Veuillez me donner un exemple de code qui explique la différence entre ces deux méthodes.

testCoder
la source

Réponses:

206

Lorsque vous avez une instance de délégué, vous pouvez connaître le type exact ou simplement savoir qu'il s'agit d'un fichier Delegate. Si vous connaissez le type exact, vous pouvez utiliser Invoke, ce qui est très rapide - tout est déjà pré-validé. Par exemple:

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

Toutefois! Si vous savez juste que c'est le cas Delegate, il doit résoudre les paramètres, etc. manuellement - cela peut impliquer un déballage, etc. - beaucoup de réflexion est en cours. Par exemple:

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

Notez que j'ai écrit la argslongue main pour indiquer clairement qu'un object[]est impliqué. Il y a beaucoup de coûts supplémentaires ici:

  • le tableau
  • la validation des arguments passés est un "ajustement" pour le réel MethodInfo
  • déballage, etc. si nécessaire
  • réflexion-invoquer
  • alors l'appelant doit faire quelque chose pour traiter la valeur de retour

En gros, évitez DynamicInvokequand vous le pouvez. Invokeest toujours préférable, sauf si tout ce que vous avez est un Delegateet un object[].

Pour une comparaison des performances, les éléments suivants en mode version en dehors du débogueur (un exe de console) s'impriment:

Invoke: 19ms
DynamicInvoke: 3813ms

Code:

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);
Marc Gravell
la source
3
Cela signifie-t-il qu'en cas d'utilisation, le compilateur DynamicInvoke produit plus de code IL pour gérer l'appel de délégué?
testCoder
2
@testCoder non, il utilisera la réflexion
Marc Gravell
@MarcGravell lorsque j'essaie cela dans une méthode qui déclenche un événement, je reçois le premier appel de méthode prend environ 0,7766 ms, mais le second prend environ 0,0568 ms. Lorsque le premier est Invoke, cela prend plus de temps que DynamicInvoke ou vice versa. Quand j'ai essayé votre exemple avec 1 boucle et regardez ms Invoke: 0,0478ms, DynamicInvoke: 0,053ms. Pourquoi les comparez-vous plus d'un appel? Et pourquoi le premier prend plus de temps que le deuxième appel de fonction?
uzay95
4
@ uzay95 Le premier appel à la méthode provoque la compilation JIT par le CLR - cela s'applique à toute méthode la première fois qu'elle est appelée après le démarrage du processus. Dans ce type de scénario, vous pouvez faire l'une des trois choses suivantes: (1) exécuter la méthode plusieurs fois afin que le temps nécessaire au premier appel devienne insignifiant dans le résultat final, (2) ne commencez pas à mesurer avant d'avoir 'ai appelé la méthode une fois, ou (3) utilisez ngen.exe (overkill). Cet article l'explique assez bien ... stackoverflow.com/questions/4446203/…
Quanta
@ marc-gravell Vous n'avez pas besoin de créer un tableau à passer à DynamicInvoke car sa signature de méthode indique le mot-clé params pour le paramètre args.
zodo le