Po pierwsze, powiem tylko, że odpowiedź Jona jest poprawna. To jedna z najbardziej owłosionych części specyfikacji, tak dobra dla Jona, że nurkuje w niej najpierw głową.
Po drugie, powiem, że ta linia:
Istnieje niejawna konwersja z grupy metod na zgodny typ delegata
(podkreślenie dodane) jest głęboko mylące i niefortunne. Porozmawiam z Madsem na temat usunięcia słowa „zgodny”.
Jest to mylące i niefortunne, ponieważ wygląda na to, że odwołuje się do sekcji 15.2, „Zgodność delegowania”. W sekcji 15.2 opisano relacje zgodności między metodami i typami delegatów , ale jest to kwestia konwersji grup metod i typów delegatów , która jest inna.
Skoro już to usunęliśmy, możemy przejść przez sekcję 6.6 specyfikacji i zobaczyć, co otrzymamy.
Aby rozwiązać problem, musimy najpierw określić, które przeciążenia są odpowiednimi kandydatami . Kandydat ma zastosowanie, jeśli wszystkie argumenty są niejawnie konwertowane na formalne typy parametrów. Rozważ tę uproszczoną wersję swojego programu:
class Program
{
delegate void D1();
delegate string D2();
static string X() { return null; }
static void Y(D1 d1) {}
static void Y(D2 d2) {}
static void Main()
{
Y(X);
}
}
Przejdźmy więc przez to linijka po linijce.
Istnieje niejawna konwersja z grupy metod na zgodny typ delegata.
Omówiłem już, że słowo „kompatybilny” jest tutaj niefortunne. Iść dalej. Zastanawiamy się, kiedy wykonujemy rozdzielczość przeciążenia na Y (X), czy grupa metod X konwertuje na D1? Czy konwertuje do D2?
Biorąc pod uwagę typ delegata D i wyrażenie E, które jest klasyfikowane jako grupa metod, istnieje niejawna konwersja z E na D, jeśli E zawiera co najmniej jedną metodę, która ma zastosowanie [...] do listy argumentów utworzonej przy użyciu parametru typy i modyfikatory D, jak opisano poniżej.
Na razie w porządku. X może zawierać metodę, która ma zastosowanie z listami argumentów D1 lub D2.
Zastosowanie konwersji w czasie kompilacji z grupy metod E do typu delegata D jest opisane poniżej.
Ta linia naprawdę nie mówi nic ciekawego.
Należy zauważyć, że istnienie niejawnej konwersji z E na D nie gwarantuje, że aplikacja konwersji w czasie kompilacji zakończy się pomyślnie bez błędu.
Ta linia jest fascynująca. Oznacza to, że istnieją niejawne konwersje, które istnieją, ale mogą zostać przekształcone w błędy! To jest dziwaczna reguła języka C #. Aby chwilę odejść, oto przykład:
void Q(Expression<Func<string>> f){}
string M(int x) { ... }
...
int y = 123;
Q(()=>M(y++));
Operacja inkrementacji jest niedozwolona w drzewie wyrażeń. Jednak lambda nadal można zamienić na typ drzewa wyrażenia, nawet jeśli konwersja jest kiedykolwiek używana, jest to błąd! Zasada jest taka, że możemy chcieć później zmienić reguły dotyczące tego, co może znaleźć się w drzewie wyrażeń; zmiana tych reguł nie powinna zmieniać reguł systemu typów . Chcemy zmusić Cię do tego, aby Twoje programy były teraz jednoznaczne , więc kiedy w przyszłości zmienimy reguły drzew wyrażeń, aby były lepsze, nie wprowadzamy istotnych zmian w rozwiązywaniu przeciążeń .
W każdym razie jest to kolejny przykład tego rodzaju dziwacznej reguły. Konwersja może istnieć w celu rozwiązania problemu z przeciążeniem, ale w rzeczywistości może być błędem. Chociaż w rzeczywistości nie jest to dokładnie sytuacja, w której się tu znajdujemy.
Iść dalej:
Wybierana jest pojedyncza metoda M odpowiadająca wywołaniu metody w postaci E (A) [...] Lista argumentów A jest listą wyrażeń, z których każde jest klasyfikowane jako zmienna [...] odpowiedniego parametru w formalnej -lista-parametrów D.
DOBRZE. Więc wykonujemy rozdzielczość przeciążenia na X w odniesieniu do D1. Formalna lista parametrów D1 jest pusta, więc wykonujemy rozwiązanie przeciążenia na X () i joy, znajdujemy metodę „string X ()”, która działa. Podobnie formalna lista parametrów D2 jest pusta. Ponownie okazuje się, że „string X ()” jest metodą, która działa również tutaj.
Zasada jest taka, że określenie konwertowalności grupy metod wymaga wybrania metody z grupy metod przy użyciu rozpoznawania przeciążenia , a rozpoznawanie przeciążenia nie uwzględnia zwracanych typów .
Jeśli algorytm [...] generuje błąd, to występuje błąd w czasie kompilacji. W przeciwnym razie algorytm tworzy jedną najlepszą metodę M mającą taką samą liczbę parametrów jak D i uważa się, że konwersja istnieje.
Jest tylko jedna metoda w grupie metod X, więc musi być najlepsza. Udowodniliśmy, że istnieje konwersja z X na D1 iz X na D2.
Czy ta linia jest istotna?
Wybrana metoda M musi być zgodna z typem delegata D lub w przeciwnym razie wystąpi błąd w czasie kompilacji.
Właściwie nie, nie w tym programie. Nigdy nie osiągnęliśmy tak daleko, jak aktywacja tej linii. Ponieważ, pamiętaj, to, co tutaj robimy, to próba rozwiązania problemu z przeciążeniem na Y (X). Mamy dwóch kandydatów Y (D1) i Y (D2). Oba mają zastosowanie. Co jest lepsze ? Nigdzie w specyfikacji nie opisujemy lepkości między tymi dwoma możliwymi konwersjami .
Można by z pewnością argumentować, że poprawna konwersja jest lepsza niż taka, która powoduje błąd. W tym przypadku oznaczałoby to efektywnie stwierdzenie, że rozwiązanie przeciążenia ROZWAŻA zwracane typy, czego chcemy uniknąć. Powstaje zatem pytanie, która zasada jest lepsza: (1) zachowaj niezmiennik, zgodnie z którym rozwiązanie przeciążenia nie bierze pod uwagę typów zwracanych, czy (2) spróbuj wybrać konwersję, o której wiemy, że będzie działać na taką, o której wiemy, że nie?
To jest wezwanie do sądu. Z lambdas , możemy zrobić pod uwagę rodzaj powrotu w tego rodzaju konwersji w sekcji 7.4.3.3:
E to funkcja anonimowa, T1 i T2 to typy delegatów lub typy drzew wyrażeń z identycznymi listami parametrów, istnieje wywnioskowany zwracany typ X dla E w kontekście tej listy parametrów i jedna z następujących blokad:
T1 ma typ zwrotu Y1, a T2 ma typ zwrotu Y2, a konwersja z X na Y1 jest lepsza niż konwersja z X na Y2
T1 ma zwracany typ Y, a T2 jest zwracany jako void
Szkoda, że konwersje grup metod i konwersje lambda są pod tym względem niespójne. Jednak mogę z tym żyć.
W każdym razie nie mamy zasady „lepszości”, która określałaby, która konwersja jest lepsza, X na D1 czy X na D2. Dlatego podajemy błąd niejednoznaczności przy rozdzielczości Y (X).