2009-06-02 13 views
18

En C# Necesito poder definir un método pero hacer que devuelva uno o dos tipos de devolución. El compilador me da un error cuando trato de hacerlo, pero ¿por qué no es lo suficientemente inteligente como para saber qué método debo llamar?Método de sobrecarga de los valores de retorno

int x = FunctionReturnsIntOrString(); 

¿Por qué el compilador me impedía tener dos funciones con diferentes tipos de devolución?

+1

charlieday, ¿podría darnos algunos detalles más sobre por qué quiere un método que puede devolver múltiples tipos de devolución? Me pregunto si la clase Convertir en el espacio de nombres del sistema podría ser un buen modelo a seguir. Tiene múltiples métodos de conversión de diferentes tipos de devolución, pero codifica el tipo de devolución en el nombre del método. P.ej. ToBoolean, ToByte, ToChar, etc. –

+4

La razón se debe a que en su escenario, el tipo de información fluiría "en ambos sentidos". Un principio básico del diseño del lenguaje es que debe poder analizar el tipo de cada parte de una expresión de forma independiente. Queremos poder determinar el tipo de expresión de llamada independientemente de lo que se le asigna. La excepción a esta regla son las expresiones lambda; hacer que la información tipo "fluya en ambos sentidos" en expresiones lambda fue muy difícil. Consulte "métodos anónimos versus expresiones lambda" en mi blog para obtener más información. –

+1

puede ver mi respuesta aquí http: // stackoverflow.com/questions/1366136/overloading-on-basis-de-return-type-only/14337184 # 14337184 –

Respuesta

21

Si bien puede ser obvio en este escenario particular, hay muchos escenarios en los que esto de hecho no es obvio. Vamos a tener la siguiente API para un ejemplo

class Bar { 
    public static int Foo(); 
    public static string Foo(); 
} 

Simplemente no es posible que el compilador para saber qué Foo para llamar en los siguientes escenarios

void Ambiguous() { 
    Bar.Foo(); 
    object o = Bar.Foo(); 
    Dog d = (Dog)(Bar.Foo()); 
    var x = Bar.Foo(); 
    Console.WriteLine(Bar.Foo()); 
} 

Estos son sólo algunos ejemplos rápidos. Más problemas artificiales y malvados seguramente existen.

+1

Puede tener el mismo problema con parámetros sobrecargados y el compilador C# le da una advertencia. ¿Cuál es el problema con una advertencia sobre los tipos de devolución? – charlieday

+1

Por favor, describa un ejemplo de este tipo en C# con parámetros sobrecargados. En principio, el compilador de C# permitirá generar un mensaje de error frente a la ambigüedad. No solo elegirá uno y le dará un error. –

+1

@charlieday, no creo que la capacidad del compilador de C# para dar la advertencia sea el problema. Mi * conjetura * es que el problema está más en la línea del costo. Esta es una característica que tiene un valor limitado y una cantidad no trivial de escenarios en los que fallará rotundamente (vea mis ejemplos). Tales características a menudo se reducen debido a un bajo beneficio versus costo. Sin embargo, no trabajo en el equipo de C# y no puedo decir si esto surgió o no en una reunión de diseño. A decir verdad, ni siquiera estoy seguro de si es legal en un nivel IL. – JaredPar

2

El tipo de devolución no forma parte de la firma del método, solo el nombre y los tipos de parámetros. Debido a esto, no puede tener dos métodos que solo difieren según el tipo de devolución. Una forma de evitar esto sería que su método devuelva un objeto, luego el código de llamada debe convertirlo en una cadena int.

Sin embargo, podría ser mejor crear dos métodos diferentes, o crear una clase para devolver desde el método que puede contener un int o una cadena.

+0

Pero en IL, el tipo de devolución se usa para identificar el método ... usando ildasm puede ver el tipo de retrn especificado cuando se especifica una instrucción callvirt. – charlieday

+0

No importa lo que contenga IL, lo que importa es la especificación C#, específicamente la sección 3.6, donde dice "La firma de un método específicamente no incluye el tipo de devolución". Consulte http://msdn.microsoft.com/en-us/library/aa691131(loband).aspx – Bevan

+0

Correcto, recuerde, no existe la "resolución de sobrecarga" en MSIL. En IL, siempre debe indicar explícitamente exactamente qué método está llamando. Ese no es el caso en C#. –

10

¿Ha pensado en usar medicamentos genéricos para devolver el tipo correcto?

public T SomeFunction<T>() 
{ 
    return T 
} 

int x = SomeFunction<int>(); 
string y = SomeFunction<string>(); 

Nota: Este código no ha sido probado

+0

Esto puede ser un poco complicado a veces, pero si se hace correctamente es definitivamente el camino a seguir. Normalmente voy un paso más allá y declaro la función booleana, y el objeto devuelto se envía por referencia. De esta forma, sabrá cuándo se ha producido un error mientras evita cualquier problema de conversión. Le da la capacidad de hacerlo: if (! SomeFunction (ref returnString)) DoErrorHandling(); –

+6

este código no funciona. –

1

En C# No hay tipos de retorno overridin, IL apoya ese tipo de anular pero C# No ... todavía.

2

Porque a veces lo que realmente no se puede decir que uno debe entonces utilizar (el ejemplo que pudo, pero no todos los casos son así de claras):

void SomeMethod(int x) { ... } 
void SomeMethod(string x) { ... } 

En este contexto, si se llama a SomeMethod(FunctionReturnsIntOrString()) lo que debe el compilador ¿verdad?

9

La función que difiere solo con los valores de retorno no califica para la sobrecarga.

int x = FunctionReturnsIntOrString(); 
double y = FunctionReturnsIntOrString(); 

En el caso anterior compilador puede identificar las funciones correctas pero tenga en cuenta los casos en que los valores de retorno no se especifica, es ambigua.

FunctionReturnsIntOrString(); //int version 
FunctionReturnsIntOrString(); //double version 

El compilador no puede resolver los métodos sobrecargados aquí.

+1

Bien ... eso tiene más sentido ... valor de retorno descartado. – charlieday

3

Creo que quieres que reconsiderar seriamente lo que está haciendo y cómo, pero se puede hacer esto:

int i = FunctionReturnsIntOrString<int>(); 
string j = FunctionReturnsIntOrString<string>(); 

mediante la implementación de esta manera:

private T FunctionReturnsIntOrString<T>() 
{ 
    int val = 1; 
    if (typeof(T) == typeof(string) || typeof(T) == typeof(int)) 
    { 
     return (T)(object)val; 
    } 
    else 
    { 
     throw new ArgumentException("or some other exception type"); 
    } 
} 

pero hay tan muchas razones para no hacerlo

+0

Supongo que la razón principal para no hacerlo es porque arrojará una InvalidCastException en tiempo de ejecución si T == typeof (cadena). –

+0

+1 para dar una solución, pero también explica que esta probablemente no sea una buena práctica de programación. – ebrown

+3

Si bien huele mal, me pregunto si podría enumerar las razones por las cuales uno no debería escribir código como este. –

26

Desde el último párrafo de la sección 1.6.6 de la C# 3.0 language specification:

The signature of a method must be unique in the class in which the method is declared. The signature of a method consists of the name of the method, the number of type parameters and the number, modifiers, and types of its parameters. The signature of a method does not include the return type.

En IL dos métodos se diferencian por el tipo de retorno solo, pero fuera de la reflexión no hay medios para llamar a un método que difiere sólo ser tipo de devolución

+5

¡Gracias por el extracto de la especificación! –

+0

"La firma de un método no incluye el tipo de devolución", esta línea me iluminó. – Gaddigesh

1

Tampoco es necesario que asigne un valor a un método llamado. Por ejemplo:

int FunctionReturnsIntOrString() {return 0;} 
string FunctionReturnsIntOrString() {return "some string";} 

//some code 
//... 

int integer = FunctionReturnsIntOrString(); //It probably could have figured this one out 

FunctionReturnsIntOrString(); //this is valid, which method would it call? 
4

es posible obtener la sintaxis que se mostró en la pregunta, pero usted tiene que participar en una cantidad bastante buena de la inteligencia con el fin de llegar allí.

Vamos a solucionar el problema un poco más concretamente. El punto clave aquí, creo, es la sintaxis. Queremos que este:

int intValue = GetData(); 
string stringValue = GetData(); 
DateTime[] dateTimeArrayValue = GetData(); 

Sin embargo, no queremos que esto funcione:

double doubleValue = GetData(); 
DateTime? nullableDateTimeValue = GetData(); 

Con el fin de lograr esto, tenemos que utilizar un objeto intermediario en el valor de retorno de GetData(), cuya definición se ve así:

public class Data 
{ 
    public int IntData { get; set; } 
    public string StringData { get; set; } 
    public DateTime[] DataTimeArrayData { get; set; } 

    public MultiReturnValueHelper<int, string, DateTime[]> GetData() 
    { 
     return new MultiReturnValueHelper<int, string, DateTime[]>(
      this.IntData, 
      this.StringData, 
      this.DataTimeArrayData); 
    } 
} 

Su puesta en práctica, por supuesto, sería muy diferente, pero esto va a hacer. Ahora definamos MultiReturnValueHelper.

public class MultiReturnValueHelper<T1, T2, T3> : Tuple<T1, T2, T3> 
{ 
    internal MultiReturnValueHelper(T1 item1, T2 item2, T3 item3) 
     : base(item1, item2, item3) 
    { 
    } 

    public static implicit operator T1(MultiReturnValueHelper<T1, T2, T3> source) 
    { 
     return source.Item1; 
    } 

    public static implicit operator T2(MultiReturnValueHelper<T1, T2, T3> source) 
    { 
     return source.Item2; 
    } 

    public static implicit operator T3(MultiReturnValueHelper<T1, T2, T3> source) 
    { 
     return source.Item3; 
    } 
} 

Repita esta definición para T1, T2, T3, etc para el caso genérico.

También puede enlazar el valor de retorno helper muy de cerca con la clase o el método que lo devuelve, lo que le permite crear el mismo tipo de efecto para los indexadores, donde puede obtener y asignar un conjunto discreto de tipos. Ahí es donde lo he encontrado más útil.

Otra aplicación es para permitir la sintaxis como la siguiente:

data["Hello"] = "World"; 
data["infamy"] = new DateTime(1941, 12, 7); 
data[42] = "Life, the universe, and everything"; 

El mecanismo preciso para llevar a cabo esta sintaxis se deja como un ejercicio para el lector.

Para una solución más de propósito general a este problema (que es, creo, lo mismo que un problema unión discriminado), consulte my answer to that question.

+0

Habiendo tratado con un código como este antes: 'data [" Hello "] =" World "; data ["infamy"] = new DateTime (1941, 12, 7); data [42] = "La vida, el universo y todo"; ' No quiero volver a tener nada que ver con eso. Si alguna vez necesita una sintaxis como esa, simplemente suponga que lo está haciendo mal y debería considerar el uso de un objeto tipeado en su lugar. – aaronburro

Cuestiones relacionadas