2015-06-17 11 views
25

Biorąc pod uwagę następujący kod:Delegowanie zmiany zachowań buforowanie w Roslyn

public class C 
{ 
    public void M() 
    { 
     var x = 5; 
     Action<int> action = y => Console.WriteLine(y); 
    } 
} 

Korzystanie VS2013, .NET 4.5. Patrząc na decompiled kodu, możemy zobaczyć, że kompilator jest buforowanie delegata w miejscu połączenia:

public class C 
{ 
    [CompilerGenerated] 
    private static Action<int> CS$<>9__CachedAnonymousMethodDelegate1; 
    public void M() 
    { 
     if (C.CS$<>9__CachedAnonymousMethodDelegate1 == null) 
     { 
      C.CS$<>9__CachedAnonymousMethodDelegate1 = new Action<int>(C.<M>b__0); 
     } 
     Action<int> arg_1D_0 = C.CS$<>9__CachedAnonymousMethodDelegate1; 
    } 
    [CompilerGenerated] 
    private static void <M>b__0(int y) 
    { 
     Console.WriteLine(y); 
    } 
} 

Patrząc na tym samym kodzie decompiled w Roslyn (używając TryRoslyn) daje następujący wynik:

public class C 
{ 
    [CompilerGenerated] 
    private sealed class <>c__DisplayClass0 
    { 
     public static readonly C.<>c__DisplayClass0 CS$<>9__inst; 
     public static Action<int> CS$<>9__CachedAnonymousMethodDelegate2; 
     static <>c__DisplayClass0() 
     { 
      // Note: this type is marked as 'beforefieldinit'. 
      C.<>c__DisplayClass0.CS$<>9__inst = new C.<>c__DisplayClass0(); 
     } 
     internal void <M>b__1(int y) 
     { 
      Console.WriteLine(y); 
     } 
    } 
    public void M() 
    { 
     Action<int> arg_22_0; 
     if (arg_22_0 = C.<>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 == null) 
     { 
      C.<>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 = 
          new Action<int>(C.<>c__DisplayClass0.CS$<>9__inst.<M>b__1); 
     } 
    } 
} 

możemy teraz zobaczyć, że pełnomocnik jest teraz podnoszone do prywatnej klasy wewnątrz C, podobnego zachowania, które jesteśmy przyzwyczajeni podczas zamykania nad zmiennej instancji/pola (zamknięcia).

Wiem, że jest to szczegół wdrożenia, który może ulec zmianie w dowolnym momencie.

Wciąż zastanawiam się, jakie są zalety przeniesienia delegata na nową klasę i zapisania go tam przez zwykłe zapisanie go w witrynie połączenia?

Edit:

This issue mówi o tym samym zachowanie jak zapytałem tutaj.

+0

[Prawie, ale nie do końca dupe] (http://stackoverflow.com/questions/27725939/why-the-compiler-adds-an-extra-parameter-for-delegates-when-there-is-no- zamknięcie/27726206 # 27726206): zachowanie zmieniło się jeszcze raz? – hvd

+0

@hvd Nie do końca, nie jest to spowodowane błędem, jest to zachowanie dla danego klienta z pamięci podręcznej w Roslyn. –

+0

"Raport o błędzie"! = "Bug" :) Nigdy nie twierdziłem, że był błąd w tym drugim pytaniu, ani tutaj. – hvd

Odpowiedz

23

Tak. Najważniejszą częścią jest to, że metoda zawierająca implementację lambda jest teraz metodą instancji.

Możesz zobaczyć delegata jako pośrednika otrzymującego wywołanie instancji przez Invoke i wysyłającego to połączenie zgodnie z konwencją wywołania metody implementacji.

Należy zauważyć, że istnieją wymagania ABI dla platformy, które określają sposób przekazywania argumentów, sposób zwracania wyników, przekazywanie argumentów za pośrednictwem rejestrów, w jaki sposób przekazywane jest "to" i tak dalej. Naruszenie tych zasad może mieć zły wpływ na narzędzia, które polegają na chodzeniu po stosie, takie jak debuggery.

Teraz, jeśli metoda implementacji jest metodą instancji, jedyną rzeczą, która musi nastąpić wewnątrz delegata, jest załączyć "ten", który jest instancją delegowaną w czasie Invoke, aby być zamkniętym obiektem docelowym. W tym momencie, ponieważ wszystko inne jest już tam, gdzie musi być, delegat może przejść bezpośrednio do ciała metody implementacji. W wielu przypadkach jest to znacznie mniej pracy niż to, co musiałoby się stać, gdyby metoda implementacji była metodą statyczną.

+0

@VSdov Dzięki za wnikliwe wyjaśnienie. Jeśli wywołanie metody instancji jest mniej skuteczne, czy istnieje jakiś specjalny powód, dla którego zespół kompilatorów przeszedł przez pętlę buforowania go jako metodę * statyczną * w miejscu wywoływania? –

+2

@YuvalItzchakov - jeśli pytanie brzmi "dlaczego pierwotna implementaiton używała metod statycznych" - nie wiem na pewno. Słyszałem, że bardzo oryginalna realizacja delegatów nie była zbyt inteligentna lub perfomran i z czasem uległa poprawie. Jeśli tak jest naprawdę, może się zdarzyć, że początkowa instancja/statyczna nie robiła wystarczającej różnicy i zespół wybrał prostszy do wdrożenia wybór. – VSadov

+1

Ale wydaje się to sprzeczne z tym, co powiedziałeś w swojej odpowiedzi. Zakładam, że jeśli obecna implementacja Roslyn jest łatwiejsza między tymi dwoma, to dlaczego początkowo używano statycznej metody, która sprawiałaby, że musieliby przeskakiwać przez obręcze, aby działało? Czy też popełniłem błąd? –

14

Wciąż zastanawiam się, jakie są korzyści z podnoszenia delegata do nowej klasy i buforowanie go tam po prostu na buforowanie go w miejscu połączenia?

Tęskniłeś za jednym ważnym szczegółem - - teraz jest to metoda instancji. Uważam, że to jest klucz do tego. IIRC, okazało się, że przywołanie delegata, który został "poparty" metodą instancji, było szybsze niż wzywanie delegata wspieranego metodą statyczną - co jest motorem zmiany.

To są wszystkie pogłoski, niejasno zapamiętane z spędzania czasu z Dustinem Campbellem i Kevinem Pilch-Bissonem (obydwaj z zespołu Roslyn) w CodeMash, ale miałoby to sens, biorąc pod uwagę kod, który pokazałeś.

(I nie potwierdziły różnicę wydajności dla siebie, i to brzmi jak to do tyłu ... ale wewnętrzne CLR może być tak zabawny ...)

+1

To bardzo interesujące. Byłoby dziwne, że tworzenie klasy za pomocą metody instancji byłoby szybsze niż wywoływanie metody statycznej. Zawsze czytałem, że emitując 'call' zamiast' callvirt', powinieneś oszczędzać kilka milisekund. –

+1

@YuvalItzchakov: Tak, ale to nie będzie wywoływać 'call' ani' callvirt' - będzie to wykonywanie magii inwokacji delegatów. Jak mówię, nie znam szczegółów * dlaczego * jest szybszy. Zgłosiłem do Dustina i Kevina na Twitterze - mogą one mieć dobre wyjaśnienie :) –

+2

@IwvalItzchakov Milliseconds to niewłaściwa jednostka czasu. Będziesz mówił o nanosekundach, a nie milisekundach. – Servy