По-перше, дозвольте сказати, що відповідь Йона правильна. Це одна з найбільш волосистих частин специфікації, так що Йон хороший тим, що спочатку занурився в неї.
По-друге, дозвольте сказати, що цей рядок:
Існує неявна конверсія з групи методів у сумісний тип делегата
(наголос додано) є глибоко оманливим і невдалим. Я поговорю з Мадсом про те, щоб тут видалити слово "сумісний".
Причина цього вводить в оману та невдало - це виглядає так, що це викликає розділ 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 - це перелік виразів, кожен класифікований як змінна [...] відповідного параметра у формальній формі -параметр-список Д.
ГАРАЗД. Таким чином, ми робимо роздільну здатність перевантаження на 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 - типи делегатів або типи дерев виразів з однаковими списками параметрів, для E в контексті цього списку параметрів існує певний тип повернення X, і одне з наступних дій:
T1 має тип повернення Y1, а T2 має тип повернення Y2, і перетворення з X в Y1 краще, ніж перетворення з X в Y2
T1 має тип повернення Y, а T2 - повернення недійсним
Прикро, що групові перетворення та лямбда-конверсії в цьому відношенні суперечливі. Однак я можу з цим жити.
У будь-якому випадку, у нас немає правила "кращості", щоб визначити, яка конверсія краща, від X до D1 або від X до D2. Тому ми даємо неоднозначну помилку щодо роздільної здатності Y (X).