Общим методам в.NET нельзя было вывести их типы возврата. Почему?

Данный:

static TDest Gimme<TSource,TDest>(TSource source) 
{ 
    return default(TDest); 
}

Почему не может я делать:

string dest = Gimme(5);

не получая ошибку компилятора:

error CS0411: The type arguments for method 'Whatever.Gimme<TSource,TDest>(TSource)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

5 может быть выведен как int, но существует ограничение, где компилятор/может, разрешают тип возврата как a string. Я читал в нескольких местах, что это дизайном, но никаким реальным объяснением. Я считал где-нибудь, что это могло бы измениться в C# 4, но он не имеет.

Кто-либо знает, почему типы возврата не могут быть выведены из общих методов? Эти из тех вопросов то, где ответ, настолько очевидный, это бросается Вам в глаза?Надеюсь, что нет!

50
задан John Saunders 28 August 2010 в 16:44
поделиться

5 ответов

Общий принцип здесь заключается в том, что информация о типах передается только «в одну сторону», от внутри к ] вне выражения. Приведенный вами пример чрезвычайно прост. Предположим, мы хотим, чтобы информация о типе передавалась «в обе стороны» при выполнении вывода типа для метода RG (A a) , и рассмотрим некоторые из безумных сценариев, которые создают:

N(G(5))

Предположим, существует десять различных перегрузок N, каждая с различным типом аргумента. Должны ли мы сделать десять различных выводов для R? Если да, то должны ли мы как-то выбрать «лучший»?

double x = b ? G(5) : 123;

Каким должен быть тип возвращаемого значения G? Int, потому что другая половина условного выражения - int? Или удвоить, потому что в конечном итоге эта вещь будет удвоена? Теперь, возможно, вы начинаете понимать, как это происходит; если вы собираетесь сказать, что рассуждаете извне внутрь, как далеко вы заходите ? На этом пути может быть много шагов. Посмотрите, что произойдет, когда мы начнем их комбинировать:

N(b ? G(5) : 123)

Что нам теперь делать? У нас есть десять перегрузок N на выбор. Мы говорим, что R - это int? Это может быть int или любой тип, в который неявно преобразуется int.Но какие из этих типов можно неявно преобразовать в тип аргумента N? Напишем ли мы себе небольшую программу пролога и попросим механизм пролога решить, какие все возможные типы возвращаемого значения могут иметь R, чтобы удовлетворить каждую из возможных перегрузок на N, а затем каким-то образом выбрать лучший из них?

(Я не шучу; есть языки, которые, по сути, пишут небольшую программу пролога, а затем используют логический механизм для определения типов всего. Например, F # делает гораздо больше сложный вывод типов, чем в C #. Система типов Haskell на самом деле является полной по Тьюрингу; вы можете кодировать произвольно сложные проблемы в системе типов и просить компилятор решить их. Как мы увидим позже, то же самое верно и для разрешения перегрузки в C # - вы не можете закодировать проблему остановки в системе типов C #, как вы можете в Haskell, но вы можете закодировать проблемы NP-HARD в проблемы разрешения перегрузки.)

Это все еще очень простое выражение. Предположим, у вас есть что-то вроде

N(N(b ? G(5) * G("hello") : 123));

. Теперь нам нужно решить эту проблему несколько раз для G, а возможно, и для N, и мы должны решить их в комбинации . У нас есть пять задач разрешения перегрузки, которые нужно решить, и все из них, если честно, должны учитывать как свои аргументы, так и тип контекста. Если есть десять возможностей для N, то потенциально есть сотня возможностей для рассмотрения для N (N (...)) и тысяча для N (N (N (...))), и очень быстро вы попросите нас решить проблемы, которые легко имели миллиарды возможных комбинаций и делали компилятор очень медленным.

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

Обратите внимание, что информация о типах для лямбда-выражений передается в обоих направлениях! Если вы скажете N (x => x.Length) , то, конечно же, мы рассмотрим все возможные перегрузки N, которые имеют типы функций или выражений в своих аргументах, и попробуем все возможные типы для x. И, конечно же, есть ситуации, в которых вы можете легко заставить компилятор опробовать миллиарды возможных комбинаций , чтобы найти уникальную комбинацию, которая работает. Правила вывода типов, которые позволяют делать это для универсальных методов, чрезвычайно сложны и заставляют нервничать даже Джона Скита. Эта функция делает разрешение перегрузки NP-HARD .

Получение информации о типах для двустороннего потока лямбда-выражений, чтобы общее разрешение перегрузки работало правильно и эффективно, заняло у меня около года. Это настолько сложная функция, что мы хотели использовать ее только в том случае, если мы абсолютно уверены, что получим потрясающую отдачу от этих инвестиций. Заставить LINQ заработать того стоило. Но нет соответствующей функции, такой как LINQ, которая оправдывала бы огромные затраты на выполнение этой работы в целом.

87
ответ дан 7 November 2019 в 10:49
поделиться

Вызов Gimme(5) без учета возвращаемого значения является законным утверждением, как компилятор узнает, какой тип возвращать?

5
ответ дан 7 November 2019 в 10:49
поделиться

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

0
ответ дан 7 November 2019 в 10:49
поделиться

Вы должны сделать:

string dest = Gimme<int, string>(5);

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

System.String - плохой пример, потому что это запечатанный класс, но предположим, что это не так. Как компилятор мог узнать, что вы не хотите получить вместо него один из его подклассов, если вы не указали тип в вызове?

Возьмем этот пример:

System.Windows.Forms.Control dest = Gimme(5);

Как компилятор узнает, какой элемент управления нужно сделать? Вам нужно указать его так:

System.Windows.Forms.Control dest = Gimme<int, System.Windows.Forms.Button>(5);
7
ответ дан 7 November 2019 в 10:49
поделиться

Полагаю, это было дизайнерское решение. Я также нахожу это полезным при программировании на Java.

В отличие от Java, C#, похоже, развивается в сторону функционального языка программирования, и вы можете получить вывод типа наоборот, так что у вас может быть:

var dest = Gimme<int, string>(5);

который выведет тип dest. Я полагаю, что смешение этого и вывода типа в стиле java может оказаться довольно сложным для реализации.

1
ответ дан 7 November 2019 в 10:49
поделиться