Realmente impossível usar o tipo de retorno de sobrecarga?

Eu fiz uma pequena DLL no MSIL com dois methods:

float AddNumbers(int, int) int AddNumbers(int, int) 

Como alguns de vocês devem saber, o MSIL permite que você crie methods que tenham os mesmos argumentos, desde que você tenha tipos diferentes de tipos de retorno (o que é chamado de sobrecarga de tipo de retorno). Agora, quando tentei usá-lo a partir de c #, como eu estava esperando, ele disparou um erro:

 float f = ILasm1.MainClass.AddNumbers(1, 2); 

O erro é:

A chamada é ambígua entre os seguintes methods ou propriedades: ‘ILasm1.MainClass.AddNumbers (int, int)’ e ‘ILasm1.MainClass.AddNumbers (int, int)’

O c # é realmente incapaz de distinguir entre diferentes tipos de retorno? Eu sei que não posso programar methods que têm como única diferença diferentes tipos de retorno, mas eu sempre presumi que saberia como lidar com isso.

Como todo mundo já disse, não C # não suporta isso. Na verdade, o motivo pelo qual o IL suporta isso é porque você precisa ser explícito sobre os tipos de retorno, assim como os parâmetros. Por exemplo, em IL você diria

 ldarg.0 ldarg.1 call int AddNumbers(int, int) 

O IL não tem realmente uma noção de sobrecarga de método: float AddNumbers(int, int) não tem relação com int AddNumbers(int, int) , no que diz respeito a IL. Você tem que dizer ao compilador IL tudo com antecedência, e ele nunca tenta inferir sua intenção (como linguagens de alto nível como C #).

Note que a maioria das linguagens .NET e C # faz uma exceção para retornar o tipo de sobrecarga: operadores de conversão. assim

 public static explicit operator B(A a); public static explicit operator C(A a); 

são compilados para

 public static B op_Explicit(A a); public static C op_Explicit(A a); 

Como esse é um caso específico, que deve ser suportado para tipos primitivos (como int -> bool) e conversões de tipo de referência (caso contrário você obteria uma linguagem muito pedante), isso é tratado, mas não como um caso de sobrecarga de método.

ECMA-334 C # Seção 8.7.3

A assinatura de um método consiste no nome do método e no número, modificadores e tipos de seus parâmetros formais. A assinatura de um método não inclui o tipo de retorno.

Você poderia usar um método genérico:

 T AddNumbers(int a, int b) { if (typeof(T) == typeof(int) || typeof(T) == typeof(float)) { return (T)Convert.ChangeType(a + b, typeof(T)); } throw new NotSupportedException(); } 

Sim, realmente não é possível em C #, eu sei que o C ++ também não permite isso, tem a ver com a maneira como uma instrução é interpretada:

 double x = AddNumbers(1, 2); 

A regra aqui é que a atribuição é associativa à direita, significando que a expressão à direita é completamente avaliada primeiro e só então a tarefa é considerada, aplicando conversões implícitas quando necessário.

Não há como o compilador determinar qual versão é mais apropriada. Usando alguma regra arbitrária seria apenas convidar muito para encontrar erros.

Está relacionado com esta simples declaração:

 double y = 5 / 2; // y = 2.0 

O problema é que existe uma conversão automática de int para float para que ele realmente não saiba o que você pretendia. Você pretendia chamar o método que leva dois ints e retorna um int, então converta-o para float ou você pretende chamar o método que leva dois ints e retorna um float? É melhor ter um erro de compilador do que fazer a escolha errada e não informá-lo até que o aplicativo seja interrompido.

O MSIL sendo capaz de manter os dois methods no mesmo assembly não tem nada a ver com o compilador determinar qual deles chamar.

Tipos de retorno Covariant foram discutidos por muitos anos, eles são parcialmente permitidos em Java, C ++ tem um caso especial e os designers C # adicionaram algumas pequenas alterações a 4.0 .

Para o seu problema de brinquedo, existem soluções típicas simples. Por exemplo, o seu float AddNumbers(int, int) provavelmente será sempre o mesmo que (float) AddNumbers(int, int) , caso em que não há necessidade da segunda function. Normalmente, esse caso é tratado com genéricos: AddNumbers( n1, n2) , então você acabaria com float AddNumbers(float, float) e int AddNumbers(int, int) .

Um cenário da vida real é mais provável de ser onde você tem diferentes representações que deseja retornar, mas não deseja renomear o método. No caso de uso de inheritance / substituição, você também pode resolver isso com genéricos .

Nos poucos casos em que eu queria fazer o que você quer, na verdade, é melhor nomear os methods de forma mais apropriada, já que é mais legível e fácil de manter no longo prazo.

Isso também foi discutido aqui .

O caso em MSIL / C # em http://blogs.msdn.com/abhinaba/archive/2005/10/07/478221.aspx é especial porque são operadores de conversão explícitos.

Não, não há como fazer isso. De fato, exceto em C ++ com templates, nenhum idioma o suporta. Isso é simplesmente muito perigoso. E novamente, e se você escrever

 var a = AddNumbers(1, 1); 

Que tipo de a supor ser?

E se você ligar assim?

  double a = AddNumbers(1, 1); 

ou até mesmo

  AddNumbers(1, 1); 

qual versão ele deveria chamar?

Lembre-se de que há regras bastante complicadas sobre como um tipo pode ser implicitamente convertido em outro. Vamos dar uma olhada no programa simples que não compila

 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); } } 

Se você tentar compilá-lo, o compilador lhe dará um erro

 error C2668: 'parse' : ambiguous call to overloaded function could be 'float parse(float)' 

porque 1.0 tem o tipo double e o compilador realmente não sabe qual tipo escolher entre int e float então ele pede para você dar uma dica. Então você pode ir em frente e converter um argumento antes de chamar uma function.

Mas se foi o tipo de retorno que a function está sobrecarregada, como você faz isso então? Simplesmente não há como fazer isso.

Que tal passar o tipo de retorno como um parâmetro usando pointers?

 void AddNumbers(int a, int b, float *ret){ *ret = (float)(a + b); } void AddNumbers(int a, int b, int *ret){ *ret = (int)(a + b); } 

Chamar isso se tornaria algo assim:

 int a; float b; AddNumbers(1, 2, &a); AddNumbers(1, 2, &b);