Ковариантность и контравариантность не работают при назначении анонимного метода для делегирования - PullRequest
0 голосов
/ 21 февраля 2019

У меня есть следующий код, взятый из этого MSDN :

public class First { }  
public class Second : First { }  
public delegate First SampleDelegate(Second a);

// Matching signature.  
public static First ASecondRFirst(Second first)  
{ return new First(); }  

// The return type is more derived.  
public static Second ASecondRSecond(Second second)  
{ return new Second(); }  

// The argument type is less derived.  
public static First AFirstRFirst(First first)  
{ return new First(); }  

// The return type is more derived   
// and the argument type is less derived.  
public static Second AFirstRSecond(First first)  
{ return new Second(); }

SampleDelegate test;
test = ASecondRFirst;
test = ASecondRSecond;
test = AFirstRFirst;
test = AFirstRSecond;

Все это прекрасно компилируется, но я хотел проверить назначение делегата анонимному лямбда-выражению:

test = (First x) => { return new Second(); };

Однако в этой строке я получаю сообщение об ошибке:

Cannot convert lambda expression to type 'SampleDelegate' because the parameter types do not match the delegate parameter types

А именно: Parameter 1 is declared as type 'ConsoleApp1.First' but should be ConsoleApp1.Second' («ConsoleApp1» - это название проекта).

Я не могу понять, что не так с моей лямбдой.

Очевидно, что ковариация, контравариантность, антивариантность и так далее работают нормально, похоже, это просто проблема с моей лямбдой.

1 Ответ

0 голосов
/ 21 февраля 2019

Быть скучным человеком, который дает ответ «потому что это то, что говорит спецификация» (TL; DR в конце с моими мыслями) ...

По сути, это потому, что преобразования групп методов (например, назначение)метод делегата) и преобразования анонимных функций (например, назначение лямбды делегату) следуют другим правилам, и только первые получают преимущество от дисперсии.

(Обратите внимание, что Method Group означает группу из 1 илибольше перегрузок одного и того же метода - поэтому ваши отдельные методы по-прежнему считаются отдельными группами методов)

В разделе 6.5 спецификации языка C # говорится о преобразованиях анонимных функций:

Выражение анонимного метода или лямбда-выражения классифицируется как анонимная функция (§7.15).Выражение не имеет типа, но может быть неявно преобразовано в совместимый тип делегата или тип дерева выражений.В частности, анонимная функция F совместима с типом делегата D при условии:

  • ...
  • Если F имеет список параметров с явным типом ввода, каждый параметр в D имеет тот же типи модификаторы как соответствующий параметр в F.

В разделе 6.6, однако, говорится о преобразованиях группы методов:

Неявное преобразование (§6.1) существует из методагруппа (§7.1) для совместимого типа делегата.Учитывая тип делегата D и выражение E, которое классифицируется как группа методов, существует неявное преобразование из E в D, если E содержит хотя бы один метод, который применим в своей нормальной форме (§7.5.3.1) к построенному списку аргументовс использованием типов параметров и модификаторов D, как описано ниже.

Применение во время компиляции преобразования из группы методов E в тип делегата D описано ниже.Обратите внимание, что существование неявного преобразования из E в D не гарантирует, что применение преобразования во время компиляции будет выполнено без ошибок.

  • Выбран один метод M, соответствующий вызову метода (§7.6.5.1) формы E (A) со следующими модификациями:
    • Список аргументов A представляет собой список выражений, каждое из которых классифицируется как переменная, с типом и модификатором (ref или out)соответствующего параметра в списке формальных параметров D.
    • Рассмотренными методами-кандидатами являются только те методы, которые применимы в их обычной форме (§7.5.3.1), а не те, которые применимы только в их расширенной форме.

Таким образом, группа методов -> преобразование делегата использует более или менее те же правила, как если бы вы пытались вызвать метод с соответствующими типами параметров.Мы направлены в Раздел 7.6.5.1, который направляет нас в Раздел 7.5.3.1.Это становится сложным, поэтому я не собираюсь вставлять его дословно здесь.

Интересно, что я не смог найти раздел о ковариации делегатов, только ковариации интерфейса (хотя в разделе 6.6 сказано упомянуть об этом в примере).


TL; DR, когда вы пишете:

SampleDelegate test = SomeMethodGroup;

, компилятор проходит весь алгоритм, чтобы выбрать подходящий член группы методов, который совместим с типом делегата., следуя тем же правилам, что и разрешение перегрузки, если вы вызывали метод.

Когда вы пишете:

SampleDelegate test = (First first) => new Second();

, компилятор следует гораздо более простому правилу: «соответствует ли сигнатура лямбдыподпись делегата ".

Полагаю, это имеет смысл.Большую часть времени вы будете писать:

SampleDelegate test = first => new Second();

, и компилятор должен выяснить типы параметров из сигнатуры делегата.Если вы добавляете явные типы самостоятельно, это не меняет полностью используемый алгоритм: компилятор использует тот же алгоритм, но если типы сталкиваются с конфликтами с вашими явными типами, вы получаете ошибку.


Обратите внимание, что почти все время это не имеет значения .Редко ставить типы в параметры лямбды, поэтому вы обычно пишете так:

SampleDelegate test = x => new Second();

Компилятор делает вывод, что x на самом деле Second, и это нормально: если вы написалилямбда, которая может работать, если x является First, она также должна работать, если x является Second (несмотря на LSP).Обратите внимание, что вам разрешено возвращать Second, хотя SampleDelegate возвращает первое: компилятор не возражает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...