На самом деле невозможно использовать перегрузку возвращаемого типа?
Я сделал небольшую DLL в MSIL двумя способами:
float AddNumbers(int, int)
int AddNumbers(int, int)
Как некоторые из вас могут знать, MSIL позволяет вам создавать методы с теми же аргументами, пока у вас есть разные типы возвращаемых типов (что называется перегрузкой типа возвращаемого типа). Теперь, когда я попытался использовать его из С#, как я и ожидал, он выпустил ошибку:
float f = ILasm1.MainClass.AddNumbers(1, 2);
Ошибка:
Вызов неоднозначен между следующими методами или свойствами: 'ILasm1.MainClass.AddNumbers(int, int)' и 'ILasm1.MainClass.AddNumbers(int, int)'
Действительно ли С# не способен различать разные типы возвращаемых данных? Я знаю, что не могу программировать методы, которые имеют только разные типы возвращаемых данных, но я всегда предполагал, что он будет знать, как с ним справиться.
Ответы
Ответ 1
Как и все остальные, ни один С# не поддерживает это. Фактически, причина, по которой IL поддерживает это, заключается в том, что вы должны быть явно о типах возвращаемых данных, точно так же, как и параметры. Например, в IL вы скажете
ldarg.0
ldarg.1
call int AddNumbers(int, int)
IL действительно не имеет понятия перегрузки метода: float AddNumbers(int, int)
не имеет отношения к int AddNumbers(int, int)
, что касается IL. Вы должны заранее сказать компилятору IL, и он никогда не пытается сделать вывод о намерениях (например, языки более высокого уровня, такие как С# do).
Обратите внимание, что большинство языков .NET и С# делают одно исключение для перегрузки типа возвращаемого типа: операторы преобразования. Так
public static explicit operator B(A a);
public static explicit operator C(A a);
скомпилированы в
public static B op_Explicit(A a);
public static C op_Explicit(A a);
Поскольку это такой конкретный краевой случай, который должен поддерживаться для примитивных типов (например, int → bool) и конверсий ссылочного типа (в противном случае вы получите очень педантичный язык), это обрабатывается, но не как случай перегрузки метода.
Ответ 2
ECMA-334 С# Раздел 8.7.3
Подпись метода состоит из имя метода и номер, модификаторы и типы его формальных параметры. Подпись метода не включает тип возврата.
Можно использовать общий метод:
T AddNumbers<T>(int a, int b)
{
if (typeof(T) == typeof(int) || typeof(T) == typeof(float))
{
return (T)Convert.ChangeType(a + b, typeof(T));
}
throw new NotSupportedException();
}
Ответ 3
Да, это действительно невозможно в С#, я знаю, что С++ тоже этого не допускает, это связано с тем, как интерпретируется оператор:
double x = AddNumbers(1, 2);
Правило здесь состоит в том, что назначение является право-ассоциативным, то есть выражение справа полностью оценивается сначала, а только тогда рассматривается присваивание, применяя при необходимости неявные преобразования.
Компилятор не может определить, какая версия наиболее подходит. Используя какое-то произвольное правило, просто будет сложно найти ошибки.
Это связано с этим простым утверждением:
double y = 5 / 2; // y = 2.0
Ответ 4
Проблема в том, что существует автоматическое преобразование из int в float, поэтому оно действительно не знает, что вы намеревались. Вы намеревались вызвать метод, который принимает два ints и возвращает int, а затем преобразовать его в float или вы намеревались вызвать метод, который принимает два int и возвращает float? Лучше иметь ошибку компилятора, чем делать неправильный выбор и не сообщать об этом до тех пор, пока ваше приложение не сломается.
Ответ 5
MSIL, способный удерживать оба метода в одной и той же сборке, не имеет ничего общего с компилятором, определяющим, какой из них вызывать.
Ковариантные типы возвращаемых данных обсуждались в течение многих лет, они частично разрешены на Java, у С++ есть специальный случай, а дизайнеры С# добавили некоторые незначительные изменяется на 4.0.
Для вашей игрушечной проблемы существуют простые типичные решения. Например, ваш float AddNumbers(int, int)
, вероятно, всегда будет таким же, как (float) AddNumbers(int, int)
, и в этом случае нет необходимости в второй функции. Обычно этот случай обрабатывается с помощью дженериков: <T> AddNumbers(<T> n1, <T> n2)
, поэтому вы получите float AddNumbers(float, float)
и int AddNumbers(int, int)
.
Сценарий реальной жизни, скорее всего, будет иметь место, где у вас есть разные представления, которые вы хотите вернуть, но вы не хотите переименовывать метод. В случае использования наследования/переопределения вы также можете решить этот несколько с помощью дженериков.
В тех немногих случаях, когда я хотел делать то, что вам нужно, на самом деле оказалось лучше назвать методы более подходящим образом, поскольку в конечном итоге он более читабельный и поддерживаемый.
Об этом уже говорилось здесь.
Случай в MSIL/С# в http://blogs.msdn.com/abhinaba/archive/2005/10/07/478221.aspx является особенным, поскольку они являются явными операторами преобразования.
Ответ 6
Нет, нет способа сделать это. На самом деле, кроме С++ с шаблонами, язык не поддерживает его. Это просто слишком опасно. И снова, что, если вы пишете
var a = AddNumbers(1, 1);
какой тип a
должен быть?
Или, если вы называете это
double a = AddNumbers(1, 1);
или даже
AddNumbers(1, 1);
какую версию он должен назвать?
Помните, что довольно сложно усложнять правила о том, как один тип может быть неявно преобразован в другой. Позвольте взглянуть на простую программу, которая не компилирует
class Program
{
static int parse(int a) { return a; }
static float parse(float a) { return a; }
static void Main(string[] args)
{
double a = parse(1.0);
}
}
Если вы попытаетесь скомпилировать его, компилятор даст вам сообщение об ошибке
error C2668: 'parse' : ambiguous call to overloaded function
could be 'float parse(float)'
потому что 1.0 имеет тип double
, и компилятор действительно не знает, какой тип выбрать между int
и float
, поэтому он просит вас дать ему подсказку. Поэтому вы можете просто перейти к преобразованию аргумента перед вызовом функции.
Но если это был тип возврата, функция перегружена, как вы это делаете? Просто нет способа сделать это.
Ответ 7
Как насчет передачи возвращаемого типа в качестве параметра с помощью указателей?
void AddNumbers(int a, int b, float *ret){
*ret = (float)(a + b);
}
void AddNumbers(int a, int b, int *ret){
*ret = (int)(a + b);
}
Вызов: это будет примерно так:
int a;
float b;
AddNumbers(1, 2, &a);
AddNumbers(1, 2, &b);