Ответ 1
Прежде всего, позвольте мне сказать, что ответ Джона верен. Это одна из самых причудливых частей спектакля, так хорошо на Джоне, чтобы сначала погрузиться в голову.
Во-вторых, позвольте мне сказать, что эта строка:
Неявное преобразование существует из группы методов в совместимый тип делегата
(выделение добавлено) глубоко вводит в заблуждение и печально. Я поговорю с Мэдсом о том, чтобы удалить здесь слово "совместимый".
Причина, по которой это вводит в заблуждение и печальна, состоит в том, что похоже, что это вызывает раздел 15.2 "Совместимость делегатов". В разделе 15.2 описано соотношение совместимости между методами и типами делегатов, но это вопрос конвертируемости групп методов и типов делегатов, что отличается.
Теперь, когда у нас это получилось, мы можем пройти через раздел 6.6 спецификации и посмотреть, что получим.
Чтобы сделать разрешение перегрузки, нам нужно сначала определить, какие перегрузки являются применимыми кандидатами. Кандидат применим, если все аргументы неявно конвертируются в формальные типы параметров. Рассмотрим эту упрощенную версию вашей программы:
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);
}
}
Итак, пропустите его по строкам.
Неявное преобразование существует из группы методов в совместимый тип делегата.
Я уже обсуждал, как слово "совместимый" здесь неудачно. Двигаемся дальше. Нам интересно, когда вы делаете разрешение перегрузки на Y (X), не превращает ли группа методов X в D1? Преобразует ли он в D2?
Учитывая тип делегата D и выражение E, которое классифицируется как группа методов, неявное преобразование существует от E до D, если E содержит в хотя бы один метод, применимый [...] к список аргументов, построенный с использованием типы параметров и модификаторы D, как описано ниже.
Пока все хорошо. X может содержать метод, который применим к спискам аргументов D1 или D2.
Применение приложения времени преобразования из группы методов E в тип делегата D описано ниже.
В этой строке нет ничего интересного.
Обратите внимание, что существование неявного преобразования из E в D не гарантирует, что приложение преобразования времени компиляции будет успешным без ошибок.
Эта линия увлекательна. Это означает, что существуют неявные преобразования, которые существуют, но которые подлежат превращению в ошибки! Это странное правило С#. Чтобы отвлечься, вот пример:
void Q(Expression<Func<string>> f){}
string M(int x) { ... }
...
int y = 123;
Q(()=>M(y++));
Операция приращения в дереве выражений является незаконной. Тем не менее, лямбда по-прежнему конвертируется в тип дерева выражений, хотя, если преобразование когда-либо используется, это ошибка! Принцип здесь заключается в том, что мы, возможно, захотим изменить правила того, что может появиться в дереве выражений позже; изменение этих правил не должно изменять системные правила типа. Мы хотим заставить вас сделать ваши программы недвусмысленными сейчас, так что, когда мы изменим правила для деревьев выражений в будущем, чтобы сделать их лучше, мы не вносим изменений в разрешение перегрузки.
Во всяком случае, это еще один пример такого странного правила. Преобразование может существовать для целей разрешения перегрузки, но быть ошибкой для фактического использования. Хотя на самом деле это не совсем та ситуация, в которой мы здесь.
Перемещение:
Выбран один метод M, соответствующий вызову метода формы E (A) [...] Список аргументов A представляет собой список выражений, каждый из которых классифицируется как переменная [...] соответствующего параметра в списке формальных параметров D.
OK. Поэтому мы перегружаем разрешение на X относительно D1. Формальный список параметров D1 пуст, поэтому мы выполняем перегрузочное разрешение на X() и радость, мы находим метод "string X()", который работает. Аналогично, формальный список параметров D2 пуст. Опять же, мы обнаруживаем, что "строка X()" - это метод, который также работает здесь.
Принцип здесь заключается в том, что определение конвертируемой группы объектов требует выбора метода из группы методов с использованием разрешения перегрузки, а разрешение перегрузки не рассматривает типы возвращаемых данных.
Если алгоритм [...] вызывает ошибку, возникает ошибка времени компиляции. В противном случае алгоритм создает единственный лучший метод M, имеющий такое же количество параметров, как D, и считается, что преобразование существует.
В группе методов X есть только один метод, поэтому он должен быть лучшим. Мы успешно доказали, что преобразование существует от X до D1 и от X до D2.
Теперь, соответствует ли эта строка?
Выбранный метод M должен быть совместим с типом делегата D или иначе возникает ошибка времени компиляции.
Собственно, нет, не в этой программе. Мы никогда не добираемся до активации этой линии. Потому что, помните, то, что мы делаем здесь, пытается сделать разрешение перегрузки на Y (X). У нас есть два кандидата Y (D1) и Y (D2). Оба варианта применимы. Что лучше? Нигде в описании мы не описываем лучшую между этими двумя возможными преобразованиями.
Теперь можно было бы утверждать, что правильное преобразование лучше, чем допустимое. Тогда было бы эффективно сказать, в этом случае, что разрешение перегрузки разрешает типы возврата, чего мы хотим избежать. Вопрос в том, какой принцип лучше: (1) поддерживать инвариант, что разрешение перегрузки не учитывает типы возврата, или (2) попытаться выбрать преобразование, которое мы знаем, будет работать над тем, что, как мы знаем, не будет?
Это призыв к суждению. С lambdas мы рассматриваем тип возвращаемого типа в этих типах преобразований, в разделе 7.4.3.3:
E - анонимная функция, T1 и T2 являются типами делегатов или деревом выражений типы с идентичными списками параметров, существует допустимый тип возврата X для E в контексте этого списка параметров, и выполняется одно из следующих условий:
T1 имеет тип возврата Y1, а T2 имеет возвращаемый тип Y2, а преобразование от X до Y1 лучше, чем преобразование из X в Y2
T1 имеет возвращаемый тип Y, а T2 не возвращается
К сожалению, преобразования групп методов и лямбда-преобразования в этом отношении несовместимы. Однако я могу жить с ним.
Во всяком случае, у нас нет правила "убеждения", чтобы определить, какое преобразование лучше, от X до D1 или от X до D2. Поэтому мы даем ошибку двусмысленности при разрешении Y (X).