27 votes

Quelle est la différence entre appeler directement un délégué, utiliser DynamicInvoke et utiliser DynamicInvokeImpl?

Les documents pour DynamicInvoke et DynamicInvokeImpl disent:

Appelle dynamiquement (à liaison tardive) la méthode représentée par le délégué actuel.

Je remarque que DynamicInvoke et DynamicInvokeImpl prennent un tableau d'objets au lieu d'une liste spécifique d'arguments (qui est la partie à liaison tardive, je suppose). Mais est-ce la seule différence? Et quelle est la différence entre DynamicInvoke et DynamicInvokeImpl.

31voto

Marc Gravell Points 482669

La principale différence entre l'appelant directement (ce qui est à court de main pour Invoke(...)) et à l'aide de DynamicInvoke est le rendement; un facteur de plus de *700 par ma mesure (ci-dessous).

Avec le direct/Invoke approche, les arguments sont déjà pré-validé par la signature de la méthode, et le code existe déjà à passer à celles de la méthode directement (je dirais "qu'IL", mais il me semble que le runtime fournit directement, sans aucun IL). Avec DynamicInvoke il a besoin de les vérifier à partir de la matrice par la réflexion (c'est à dire sont-ils appropriés pour cet appel, ont-ils besoin d'unboxing, etc); c'est lent (si vous l'utilisez dans une boucle), et doit être évitée autant que possible.

Exemple; les résultats de la première (j'ai augmenté les LOOP comptent à partir de la précédente édition, pour donner un bon de comparaison):

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

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

9voto

JaredPar Points 333733

Il n'y a vraiment aucune différence fonctionnelle entre les deux. si vous récupérez l'implémentation dans le réflecteur, vous remarquerez que DynamicInvoke appelle simplement DynamicInvokeImpl avec le même ensemble d'arguments. Aucune validation supplémentaire n'est effectuée et il s'agit d'une méthode non virtuelle, il n'y a donc aucune chance que son comportement soit modifié par une classe dérivée. DynamicInvokeImpl est une méthode virtuelle où tout le travail réel est effectué.

9voto

Rainer Hilmer Points 103

Par coïncidence, j'ai trouvé une autre différence.

Si Invoke lève une exception, elle peut être interceptée par le type d'exception attendu. Cependant DynamicInvoke lance un TargetInvokationException . Voici une petite démo:

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

Alors que le deuxième test passe au vert, le premier fait face à une exception 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);
      }
   }
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X