2011-11-10 11 views
9

Na podstawie following question, znalazłem dziwne zachowanie kompilatora C#.C# nieparzystość kompilatora z delegowanymi konstruktorami

Poniżej jest ważny C#:

static void K() {} 

static void Main() 
{ 
    var k = new Action(new Action(new Action(K)))); 
} 

Co ja znajduję dziwny jest kompilator „dekonstrukcji” przekazany delegata.

Wyjście ILSpy jest następujący:

new Action(new Action(new Action(null, ldftn(K)), ldftn(Invoke)).Invoke); 

Jak widać, to automatycznie zdecyduje się zastosować metodę delegata Invoke. Ale dlaczego?

Tak jak jest, kod jest niejasny. Czy mamy potrójnie zawiniętego delegata (rzeczywistego), czy też wewnętrzny delegat właśnie "skopiował" do zewnętrznych (moja początkowa myśl).

pewnością jeśli intencją było jak kompilator emitowanego kod, należałoby napisać:

var k = new Action(new Action(new Action(K).Invoke).Invoke); 

Podobne do dekompilacji kodu.

Czy ktoś może usprawiedliwić przyczynę tej "zaskakującej" transformacji?

Aktualizacja:

mogę myśleć tylko o jednej z możliwych przypadków użycia do tego przeznaczonych; konwersja typu delegata. Np .:

delegate void Baz(); 
delegate void Bar(); 
... 
var k = new Baz(new Bar(new Action (K))); 

Prawdopodobnie kompilator powinien wysłać ostrzeżenie, jeśli używane są te same typy delegatów.

+2

Nie jest jasne, co masz na myśli przez „to wewnętrzna delegat prostu„skopiowane "do tych zewnętrznych" - czego dokładnie byś się spodziewał? Jedna instancja delegata? –

+1

W interesie, gdzie zatrudniłbyś taką konstrukcję? Wygląda na to, że "potrójnie zapakowany delegat" zjadł nieostrożnego programistę na śniadanie ... – MoonKnight

+0

@JonSkeet: Spodziewałem się, że będzie on bardziej podobny do "nowej akcji (a.Target, a.Method)" zamiast 'nowa Akcja (a.Invoke)'. IOW, bez pakowania. – leppie

Odpowiedz

5

spec (rozdział 7.6.10.5) mówi:

  • Nowy przykład pełnomocnik inicjowany jest z tej samej listy wywołania jako przykład delegata podany przez E.

teraz załóżmy, że kompilator przetłumaczył go na coś podobnego do twojej sugestii:

new Action(a.Target, a.Method) 

To utworzyłoby tylko delegata z listą wywołań jednego wywołania metody pojedynczej. W przypadku delegata multi-cast narusza on specyfikację.

Przykładowy kod:

using System; 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Action first =() => Console.WriteLine("First"); 
     Action second =() => Console.WriteLine("Second"); 

     Action both = first + second; 
     Action wrapped1 = 
      (Action) Delegate.CreateDelegate(typeof(Action), 
              both.Target, both.Method); 
     Action wrapped2 = new Action(both); 

     Console.WriteLine("Calling wrapped1:"); 
     wrapped1(); 

     Console.WriteLine("Calling wrapped2:"); 
     wrapped2(); 
    } 
} 

wyjściowa:

Calling wrapped1: 
Second 
Calling wrapped2: 
First 
Second 

Jak widać, prawdziwy zachowanie kompilatora mecze spec - sugerowaną zachowanie nie.

Wynika to częściowo ze względu na nieco dziwnym „Czasami pojedynczej obsadzie, czasami multi-cast” charakter Delegate oczywiście ...

+0

Czekaj, ten tekst jest mylący. Nowy delegat będzie tylko opakowaniem i będzie miał tylko jedną pozycję na liście wywołań. Inaczej wszystko zostanie wywołane dwa razy. – leppie

+0

Widzę jednak, że delegaci multiemisji stanowią złożony scenariusz. – leppie

+0

'Console.WriteLine (wrapped2.GetInvList(). Length);' prints 1. Spec jest naruszone (lub tylko niepoprawnie określone). – leppie

2
  • pełnomocnik jest klasa
  • Działanie pełnomocnik konstruktor jak w przykładzie

    publicznego zewnętrzny działania (przedmiotem @object, IntPtr metoda);

  • od K metodą statyczną nie ma potrzeby, aby przejść przedmiotu do najbardziej wewnętrznych przykład działanie jako pierwszy argument i stąd przechodzi zerowy

  • od drugiego argumentu jest wskaźnik działają zatem przechodzi on wskaźnik sposobu K, przy stosowaniu funkcja
  • ldftn dla pozostałych przypadkach działania obiekt jest przekazywane jest wewnętrzna Action, a drugi parametr jest metoda Invoke ponieważ podczas rozmowy delegat jesteś rzeczywiście wywołanie metody Invoke

Podsumowanie

var action = new Action(K) => Action action = new Action(null, ldftn(K)) 
new Action(action) => new Action(action, ldftn(Action.Invoke)) 

Mam nadzieję, że to wyjaśnia, co się dzieje?

+0

Pytanie, które zadaję, to dokładnie to, co mówisz w swoim ostatnim punkcie. Wiem, co się dzieje, ale dlaczego? – leppie

+0

@leppie sprawdź zaktualizowaną odpowiedź –

+0

Jeśli ponownie przeczytasz moje pytanie, zauważysz, że wszystko, co mówisz, już tam jest. Rozumiem ** co ** się dzieje. Chcę wiedzieć, jakie jest uzasadnienie (lub dlaczego) tego. – leppie

3

Podczas próby potraktowania delegata jako metody, kompilator faktycznie używa metody delegata: Invoke().Tak więc, na przykład, dwie linie poniżej kompilacji do dokładnie tej samej IL (zarówno połączenia Invoke()):

k(); 
k.Invoke(); 

Zakładam, że osobliwość widzisz jest tego konsekwencją. Konstruktor delegatów oczekuje metody (lub raczej grupy metod), ale zamiast tego otrzymuje delegata. Traktuje to więc jako metodę i używa metody Invoke().

Jeśli chodzi o znaczenie, jest to delegat, który wzywa delegata, który wywołuje właściwą metodę. Możesz to sprawdzić samodzielnie, uzyskując dostęp do właściwości delegata: Method i Target. W przypadku delegata najbardziej zewnętrznego, Method jest Action.Invoke i Target wewnętrznym delegatem.

+1

Podążam za tym, ale ten projekt sprawia, że ​​delegaci są bardziej obywatelami drugiej kategorii. Podobnie jak w niektórych kontekstach, 'k' jest naprawdę' k.Invoke', ale w innych przypadkach 'k' jest po prostu delegatem. – leppie

+0

Myślę, że to dlatego, że delegaci są właściwie (prawie) normalnymi klasami, jeśli chodzi o CLR. Aby uczynić ich pierwszorzędnymi obywatelami w C#, musisz uciekać się do takich rzeczy. – svick

+0

Normalne zajęcia to pierwszorzędni obywatele, którzy zapewniają magiczną funkcjonalność, dzięki czemu są klasą drugą (no i ta specyficzna funkcjonalność). – leppie

Powiązane problemy