Прежде всего, позвольте мне сказать, что ответ Джона правильный. Это одна из самых привлекательных частей спецификации, поэтому Джону отлично подходит для погружения в нее головой.
Во-вторых, позвольте мне сказать, что эта строка:
Существует неявное преобразование из группы методов в совместимый тип делегата
(ударение добавлено) глубоко вводит в заблуждение и вызывает сожаление. Я поговорю с Мэдсом об удалении слова «совместимый».
Причина, по которой это вводит в заблуждение и вызывает сожаление, заключается в том, что, похоже, это вызывает раздел 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.
OK. Таким образом, мы делаем разрешение перегрузки на 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) попытаться выбрать преобразование, которое, как мы знаем, будет работать над тем, которое, как мы знаем, не будет?
Это призыв к суду. С lambdas мы do рассматриваем тип возвращаемого значения в этих видах преобразований, в разделе 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).