Diferencia entre Invoke y DynamicInvoke

128

¿Cuál es la diferencia entre Invoke y DynamicInvoke en delegados? Por favor, dame un ejemplo de código que explique la diferencia entre esos dos métodos.

testCoder
fuente

Respuestas:

206

Cuando tiene una instancia de delegado, puede saber el tipo exacto, o simplemente puede saber que es un Delegate. Si conoce el tipo exacto, puede usarlo Invoke, que es muy rápido : todo ya está prevalidado. Por ejemplo:

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

¡Sin embargo! Si solo sabe que es así Delegate, tiene que resolver los parámetros, etc. manualmente, esto puede implicar unboxing, etc., se está produciendo mucha reflexión. Por ejemplo:

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

Tenga en cuenta que he escrito la argsletra larga para dejar en claro que un object[]está involucrado. Aquí hay muchos costos adicionales:

  • la matriz
  • validar los argumentos pasados ​​son un "ajuste" para el actual MethodInfo
  • desempaquetado, etc., según sea necesario
  • invocación de reflexión
  • entonces la persona que llama debe hacer algo para procesar el valor de retorno

Básicamente, evite DynamicInvokesiempre que pueda. Invokesiempre es preferible, a menos que todo lo que tenga sea un Delegatey un object[].

Para una comparación de rendimiento, se imprime lo siguiente en modo de lanzamiento fuera del depurador (un exe de consola):

Invoke: 19ms
DynamicInvoke: 3813ms

Código:

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
fuente
3
¿Significa que, en caso de uso, el compilador DynamicInvoke produce más código IL para manejar la invocación de delegado?
testCoder
2
@testCoder no, usará la reflexión
Marc Gravell
@MarcGravell cuando intento esto en un método que está generando un evento, recibo la primera llamada al método está tomando alrededor de 0,7766 ms pero la segunda toma alrededor de 0,0568 ms. Cuando el primero es Invoke, lleva más tiempo que DynamicInvoke o viceversa. Cuando probé su ejemplo con 1 bucle y mire ms Invoke: 0,0478ms, DynamicInvoke: 0,053ms. ¿Por qué los estás comparando más de 1 llamada? ¿Y por qué la primera tarda más que la segunda llamada de función?
uzay95
44
@ uzay95 La primera llamada al método hace que el CLR realice la compilación JIT; esto se aplica a cualquier método la primera vez que se llama después de que se inicia el proceso. En este tipo de escenario, puede hacer una de estas tres cosas: (1) ejecute el método varias veces para que el tiempo que tomó la primera llamada se vuelva insignificante en el resultado final, (2) no comience a medir hasta después de He llamado al método una vez, o (3) usa ngen.exe (overkill). Esta publicación lo explica bastante bien ... stackoverflow.com/questions/4446203/…
Quanta
@ marc-gravell No necesita crear una matriz para pasar a DynamicInvoke ya que su firma de método establece la palabra clave params para el parámetro args.
zodo