Подтвердить что ты не робот

У общих методов в .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, но существует ограничение, при котором компилятор не сможет/не сможет разрешить возвращаемый тип как string. Я читал в нескольких местах, что это по дизайну, но никаких реальных объяснений. Я где-то читал, что это может измениться на С# 4, но это не так.

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

4b9b3361

Ответ 1

Общий принцип здесь заключается в том, что информация типа течет только "в одну сторону", изнутри наружу выражения. Пример, который вы даете, чрезвычайно прост. Предположим, мы хотели иметь поток информации о типе "в обоих направлениях" при выполнении вывода типа в методе R G<A, R>(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 # например, делает более сложный вывод типа, чем С# Система Haskell на самом деле является Turing Complete, вы можете кодировать произвольно сложные проблемы в системе типов и попросить компилятор их решить. Как мы увидим позже, то же самое верно и для разрешения перегрузки в С# - вы не можете кодировать проблему с остановкой в системе типа С#, как вы можете в 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, которая оправдывает огромные затраты на выполнение этой работы в целом.

Ответ 2

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

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

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

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

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

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

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

System.Windows.Forms.Control dest = Gimme<int, System.Windows.Forms.Button>(5);

Ответ 3

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

Ответ 4

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

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

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

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

Ответ 5

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