Różnica między Invoke i DynamicInvoke

131

Jaka jest różnica między Invoke i DynamicInvoke w delegatach? Proszę podać przykład kodu, który wyjaśnia różnicę między tymi dwiema metodami.

testCoder
źródło

Odpowiedzi:

209

Gdy masz wystąpienie delegata, możesz znać dokładny typ lub po prostu wiedzieć, że jest to Delegate. Jeśli znasz dokładny typ, możesz użyć Invoke, co jest bardzo szybkie - wszystko jest już wstępnie sprawdzone. Na przykład:

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

Jednak! Jeśli po prostu wiesz, że tak jest Delegate, musi rozwiązać parametry itp. Ręcznie - może to wymagać rozpakowania itp. - dzieje się wiele refleksji. Na przykład:

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

Uwaga: Napisałem argsdługą rękę, aby wyjaśnić, że w object[]grę wchodzi. Istnieje wiele dodatkowych kosztów:

  • tablica
  • walidacja przekazanych argumentów jest zgodna z rzeczywistością MethodInfo
  • rozpakowywanie itp. w razie potrzeby
  • wywołanie odbicia
  • następnie wywołujący musi coś zrobić, aby przetworzyć wartość zwracaną

Zasadniczo unikaj, DynamicInvokekiedy tylko możesz. Invokejest zawsze preferowane, chyba że wszystko, co masz, to a Delegatei object[].

W celu porównania wydajności drukuje w trybie wydania poza debuggerem (exe konsoli):

Invoke: 19ms
DynamicInvoke: 3813ms

Kod:

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
źródło
3
Czy to oznacza, że ​​w przypadku użycia kompilator DynamicInvoke generuje więcej kodu IL do obsługi wywołania delegata?
testCoder
2
@testCoder nie, użyje odbicia
Marc Gravell
@MarcGravell, kiedy próbuję tego w metodzie, która wywołuje zdarzenie, otrzymuję pierwsze wywołanie metody, które trwa około 0,7766 ms, ale drugie zajmuje około 0,0568 ms. Gdy pierwszy to Invoke, trwa to dłużej niż DynamicInvoke lub odwrotnie. Kiedy próbowałem twojego przykładu z 1 pętlą i spójrz na ms Invoke: 0,0478ms, DynamicInvoke: 0,053ms. Dlaczego porównujesz je z więcej niż jednym połączeniem? I dlaczego pierwsze trwa dłużej niż drugie wywołanie funkcji?
uzay95
4
@ uzay95 Pierwsze wywołanie metody powoduje kompilację JIT przez środowisko CLR - dotyczy to każdej metody przy pierwszym wywołaniu po uruchomieniu procesu. W tego rodzaju scenariuszu możesz zrobić jedną z trzech rzeczy: (1) uruchomić metodę kilka razy, aby czas potrzebny na pierwsze wywołanie stał się nieistotny w ostatecznym wyniku, (2) nie zaczynaj pomiaru, dopóki nie już raz wywołałem tę metodę lub (3) użyj ngen.exe (overkill). Ten post wyjaśnia to wystarczająco dobrze ... stackoverflow.com/questions/4446203/…
Quanta
@ marc-gravell Nie musisz tworzyć tablicy do przekazania do DynamicInvoke, ponieważ jego podpis metody określa słowo kluczowe params dla parametru args.
zodo