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