2014-12-31 13 views
22

grałem z delegates i zauważyłem, że gdy tworzę Func<int,int,int> jak w poniższym przykładzie:Dlaczego kompilator dodaje dodatkowy parametr dla delegatów, gdy nie ma zamknięcia?

Func<int, int, int> func1 = (x, y) => x * y; 

Podpis metody kompilatora generowany jest czego się spodziewałem:

enter image description here

jako widać, że pobiera obiekt dla pierwszego parametru. Ale kiedy jest zamknięcie:

int z = 10; 
Func<int, int, int> func1 = (x, y) => x * y * z; 

Everthing działa zgodnie z oczekiwaniami:

enter image description here

ten kod IL dla metody z dodatkowym parametrem:

.method private hidebysig static int32 '<Main>b__0'(object A_0, 
                int32 x, 
                int32 y) cil managed 
{ 
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (01 00 00 00) 
    // Code size  8 (0x8) 
    .maxstack 2 
    .locals init ([0] int32 V_0) 
    IL_0000: ldarg.1 
    IL_0001: ldarg.2 
    IL_0002: mul 
    IL_0003: stloc.0 
    IL_0004: br.s  IL_0006 
    IL_0006: ldloc.0 
    IL_0007: ret 
} // end of method Program::'<Main>b__0' 

Wydaje się, że parametr A_0 nie jest jeszcze używany. Więc jaki jest cel parametru object w pierwszym przypadku? Dlaczego nie jest dodawany, gdy istnieje zamknięcie?

Uwaga: Jeśli masz lepszy pomysł na tytuł, możesz edytować.

Uwaga 2: Pierwszy skompilowany kod w trybach Debug i Release, nie było różnicy. Ale skompilowałem drugi tryb w trybie Debug, aby uzyskać zachowanie zamknięcia, ponieważ optymalizuje on zmienną lokalną w trybie Release.

Uwaga 3: Używam Visual Studio 2014 CTP.

Edit: Jest to kod wygenerowany dla Main w pierwszym przypadku:

.method private hidebysig static void Main(string[] args) cil managed 
{ 
    .entrypoint 
    // Code size  30 (0x1e) 
    .maxstack 2 
    .locals init ([0] class [mscorlib]System.Func`3<int32,int32,int32> func1) 
    IL_0000: nop 
    IL_0001: ldsfld  class [mscorlib]System.Func`3<int32,int32,int32> ConsoleApplication9.Program::'CS$<>9__CachedAnonymousMethodDelegate1' 
    IL_0006: dup 
    IL_0007: brtrue.s IL_001c 
    IL_0009: pop 
    IL_000a: ldnull 
    IL_000b: ldftn  int32 ConsoleApplication9.Program::'<Main>b__0'(object, 
                     int32, 
                     int32) 
    IL_0011: newobj  instance void class [mscorlib]System.Func`3<int32,int32,int32>::.ctor(object, 
                          native int) 
    IL_0016: dup 
    IL_0017: stsfld  class [mscorlib]System.Func`3<int32,int32,int32> ConsoleApplication9.Program::'CS$<>9__CachedAnonymousMethodDelegate1' 
    IL_001c: stloc.0 
    IL_001d: ret 
} // end of method Program::Main 
+0

Kiedy to nazwiesz, co zostanie przekazane? – siride

+0

@side w kodzie, wszystko działa zgodnie z oczekiwaniami. Nie widzę żadnego dodatkowego parametru w sygnaturze 'func' podczas wywoływania go. –

+1

Jeśli rzeczywiście istnieje parametr obiektu, to po wywołaniu lambda coś powinno zostać przekazane w tym gnieździe. Powinien przynajmniej pojawić się w IL. To tam powinieneś patrzeć. – siride

Odpowiedz

18

Chociaż może to wydawać się bardzo zaskakujące, szybkie wyszukiwanie pokazuje, że to ze względu na wydajność.

Na a bug report about it, to wskazał, że delegaci na które nie mają niejawny this są wymiernie wolniej niż delegatów, które mają niejawny this, ponieważ delegatów, które nie mają niejawny this trzeba zrobić trochę skomplikowany tasowania argumentu ilekroć delegat jest wywoływany:

Załóżmy, że dzwonisz pod numer func1(1, 2).Wygląda to (pseudo-kod, nie CIL)

push func1 
push 1 
push 2 
call Func<,,>::Invoke 

Gdy ta func1 jest znany z wiązania się do statycznego funkcji po dwa int wartości, to wówczas musi wykonać odpowiada albo

push arg.1 
push arg.2 
call method 

lub

arg.0 = arg.1 
arg.1 = arg.2 
jmp method 

Natomiast, gdy func1 wiadomo, że jest związany z funkcją statycznego biorąc null i dwa int wartości, tylko musi wykonać równowartość

arg.0 = null 
jmp method 

ponieważ środowisko jest już skonfigurowany idealnie do wprowadzania funkcji robienia typ odniesienia i dwa int wartości.

Tak, jest to mikrooptymalizacja, która zazwyczaj nie ma znaczenia, ale na tym skorzystają wszyscy, w tym także w sytuacjach, w których ma to znaczenie.

+3

Ile razy tu czytałem, że mikro-optymalizacje nie mają znaczenia :). Nigdy się z tym nie zgadzam.Uważam to za całkiem miłe. – fejesjoco

+0

Dobra odpowiedź, ale możesz wyjaśnić powieloną argumentację tasowania części dla przyszłych czytelników, otrzymuję ją, gdy czytam komentarze. :) –

+0

@ Selman22 Coś takiego? Jeśli uważasz, że coś tu przedstawiłem, zrób komentarz. – hvd

Powiązane problemy