Вот более простое воспроизведение:
interface I<T> {}
class X<T> {}
class Y {}
class Z {}
class C : I<X<Y>>, I<Z> {}
public class P
{
static void M<T>(I<X<T>> i) { }
public static void Main()
{
M(new C());
}
}
Вывод типа не выполняется. Вы спрашиваете, почему и почему всегда трудно ответить на вопросы, поэтому позвольте мне перефразировать вопрос:
Какая строка спецификации запрещает этот вывод?
У меня есть моя копия спецификации C # 3; строка там выглядит следующим образом
V
- это тип, который мы выводим от до , поэтому I<X<T>>
в данном случае
U
- это тип, который мы выводим из , поэтому C
в данном случае
Это будет немного отличаться в C # 4, потому что я добавил ковариацию, но мы можем игнорировать это для целей этого обсуждения.
... если V
является составным типом C<V1, … Vk>
и существует уникальный набор типов U1, … Uk
, такой, что существует неявное преобразование из U
в C<U1, … Uk>
, то точный вывод делается из каждого Ui
к соответствующему Vi
. В противном случае не делается никаких выводов.
Обратите внимание на слово уникальное там. НЕ существует уникального набора типов, так что C
можно преобразовать в I<Something>
, потому что оба X<Y>
и Z
действительны.
Вот еще один не-почему вопрос:
Какие факторы были учтены, когда команда дизайнеров приняла это решение?
Вы правы, что теоретически мы могли обнаружить в вашем случае, что X<Y>
предназначен, а Z
- нет. Если вы хотите предложить алгоритм вывода типа, который может обрабатывать неуникальные ситуации, подобные этой, которая никогда не допускает ошибки, - помните, Z
может быть подтипом или супертипом X<Y>
или X<Something Else>
и I
может быть ковариантным - тогда я уверен, что команда C # будет рада рассмотреть ваше предложение.
У нас был этот аргумент в 2005 году при разработке алгоритма логического вывода типа C # 3, и мы решили, что сценарии, в которых один класс реализует два одинаковых интерфейса, были редкими, а работа с этими редкими ситуациями создавала значительные сложности в языке. Эти сложности были бы дорогостоящими для разработки, определения, реализации и тестирования, и у нас были другие вещи, на которые можно было бы потратить деньги и усилия, что оказало бы большее влияние.
Кроме того, мы не знали, когда создавали C # 3, будем ли мы добавлять ковариацию в C # 4. Мы никогда не хотим вводить новую языковую функцию, которая делает возможную будущую языковую функцию невозможной или трудной . Лучше установить ограничения в языке сейчас и рассмотреть их снятие позже, чем выполнять большую работу для редкого сценария, который усложняет общий сценарий в следующей версии.
Тот факт, что я помогал спроектировать этот алгоритм и реализовывал его несколько раз, и вначале полностью не помнил это правило, должен сказать вам, как часто это происходило за последние 13 лет. Вряд ли вообще. Очень редко кто-то находится в вашей конкретной лодке, и есть простой обходной путь: укажите тип.
Вопрос, который вы не задавали, но вспоминаете:
Может ли сообщение об ошибке быть лучше?
Да. Я прошу прощения за то. Я проделал большую работу, сделав сообщения об ошибках разрешения перегрузки более описательными для распространенных сценариев LINQ, и я всегда хотел вернуться назад и сделать сообщения об ошибках логического вывода другого типа более понятными. Я написал вывод типа и код разрешения перегрузки, чтобы сохранить внутреннюю информацию, объясняющую, почему тип был выведен или перегрузка была выбрана или отклонена, как для моих собственных целей отладки, так и для создания лучших сообщений об ошибках, но я так и не удосужился разоблачить это информация для пользователя. Всегда было что-то более приоритетное.
Возможно, вы решите ввести проблему на сайте Roslyn GitHub, предложив улучшить это сообщение об ошибке, чтобы пользователям было проще диагностировать ситуацию. Опять же, тот факт, что я не сразу диагностировал проблему и должен был вернуться к спецификации, свидетельствует о том, что сообщение неясно.