2009-06-02 16 views
18

In C# Devo essere in grado di definire un metodo, ma è necessario restituire uno o due tipi di ritorno. Il compilatore mi dà un errore quando provo a farlo, ma perché non è abbastanza intelligente da sapere quale metodo devo chiamare?Valori di ritorno del metodo di sovraccarico

int x = FunctionReturnsIntOrString(); 

Perché il compilatore mi impedisce di avere due funzioni con tipi di ritorno diversi?

+1

charlieday, potresti fornire qualche altro dettaglio sul motivo per cui desideri un metodo che può restituire più tipi di reso? Mi chiedo se la classe Convert nello spazio dei nomi System potrebbe essere un buon modello da seguire. Ha più metodi di conversione di diversi tipi di ritorno ma codifica il tipo restituito nel nome del metodo. Per esempio. ToBoolean, ToByte, ToChar, ecc. –

+4

Il motivo è perché nel tuo scenario, le informazioni sul tipo sarebbero fluide "in entrambe le direzioni". Un principio di base del design del linguaggio è che dovresti essere in grado di analizzare il tipo di ogni parte di un'espressione in modo indipendente. Vogliamo essere in grado di determinare il tipo di espressione della chiamata indipendentemente da ciò a cui viene assegnato. L'eccezione a questa regola è rappresentata dalle espressioni lambda; rendere le informazioni sul tipo "flusso in entrambe le direzioni" nelle espressioni lambda era molto difficile. Vedi "metodi anonimi vs espressioni lambda" nel mio blog per i dettagli. –

+1

si può guardare la mia risposta qui http: // StackOverflow.it/questions/1366136/overloading-on-base-of-return-only/14337184 # 14337184 –

risposta

21

Mentre può essere ovvio in questo particolare scenario, ci sono molti scenari in cui questo in realtà non è ovvio. Consente di prendere la seguente API per un esempio

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

Non è semplicemente possibile per il compilatore a sapere quali Foo chiamare nei seguenti scenari

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

Questi sono solo alcuni esempi veloci. Esistono sicuramente dei problemi inventati e malvagi.

+1

È possibile avere lo stesso problema con i parametri sovraccaricati e il compilatore C# ti dà un avvertimento. Qual è il problema con un avviso sui tipi di reso? – charlieday

+1

Si prega di descrivere un tale esempio in C# con parametri sovraccaricati. In linea di principio, il compilatore C# consente di generare un messaggio di errore di fronte all'ambiguità. Non ne sceglierà uno e ti darà un errore. –

+1

@charlieday, non credo che la capacità del compilatore C# di dare l'avviso sia il problema. La mia * ipotesi * è che il problema è più lungo le linee del costo. Questa è una caratteristica che ha un valore limitato e un numero non banale di scenari in cui fallirà completamente (vedi i miei campioni). Tali caratteristiche sono spesso ridotte a causa del basso vantaggio rispetto al costo. Non lavoro però con il team C# e non posso parlare se questo sia mai venuto fuori in una riunione di design. Sinceramente, non sono nemmeno sicuro che sia legale a livello IL. – JaredPar

2

Il tipo di reso non fa parte della firma del metodo, ma solo del nome e dei tipi di parametro. Per questo motivo, non è possibile avere due metodi che differiscono solo per il tipo restituito. Un modo per aggirare il problema è che il metodo restituisca un oggetto, quindi il codice chiamante deve eseguirlo su un int o una stringa.

Tuttavia, potrebbe essere preferibile creare due metodi diversi oppure creare una classe da restituire dal metodo che può contenere una stringa int o una stringa.

+0

Ma in IL, il tipo di ritorno è usato per identificare il metodo ... usando ildasm puoi vedere il tipo retrn specificato quando è stata specificata un'istruzione callvirt. – charlieday

+0

Non importa cosa c'è in IL - ciò che conta è la specifica C#, in particolare la sezione 3.6, in cui si dice "La firma di un metodo in particolare non include il tipo restituito". Vedi http://msdn.microsoft.com/en-us/library/aa691131(loband).aspx – Bevan

+0

Giusto: ricorda che non esiste una "risoluzione di sovraccarico" in MSIL. In IL, devi sempre indicare esplicitamente quale metodo stai chiamando. Questo non è il caso di C#. –

10

Hai pensato di utilizzare i generici per restituire il tipo corretto.

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

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

Nota: Questo codice non è stato testato

+0

Questo può diventare un po 'complicato a volte, ma se fatto correttamente è sicuramente la strada da percorrere. Di solito faccio un ulteriore passo avanti e dichiaro la funzione booleana, e l'oggetto di ritorno viene inviato per riferimento. In questo modo sai quando si è verificato un errore evitando problemi di trasmissione. Ti dà il potere di fare: if (! SomeFunction (ref returnString)) DoErrorHandling(); –

+6

questo codice non funziona. –

1

In C# non c'è nessun tipo di ritorno overridin, IL supporta questo tipo di rilevante, ma C# non ... ancora.

2

Perché a volte davvero non si può dire che uno dovrebbe quindi utilizzare (il tuo esempio potrebbe, ma non tutti i casi sono che chiara):

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

In questo contesto, se si chiama SomeMethod(FunctionReturnsIntOrString()) cosa dovrebbe fare il compilatore?

9

Le funzioni che differiscono solo per i valori di ritorno non sono valide per il sovraccarico.

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

Nel caso compilatore sopra può identificare le funzioni corrette ma considerare i casi in cui restituiscono valori non sono specificate, è ambigua.

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

Il compilatore non è in grado di risolvere i metodi sovraccaricati qui.

+1

Ok ... questo ha più senso ... valore di ritorno scartato. – charlieday

3

Penso che si desidera a riconsiderare seriamente quello che stai facendo e come, ma si può fare questo:

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

implementando in questo modo:

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

ma ci sono talmente tante ragioni per non farlo

+0

Suppongo che il motivo principale per non farlo è che genererà InvalidCastException in fase di esecuzione se T == typeof (stringa). –

+0

+1 per dare una soluzione, ma anche spiegando che probabilmente non è una buona pratica di programmazione. – ebrown

+3

Mentre questo ha un cattivo odore, mi chiedo se potresti enumerare i motivi per cui non si dovrebbe scrivere codice come questo? –

26

Dalla ultimo paragrafo della sezione 1.6.6 della 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.

In IL due metodi possono differire a seconda della tipologia di ritorno da solo, ma al di fuori di riflessione non ci sono mezzi per chiamare un metodo che differisce solo essere di ritorno.

+5

Grazie per l'estratto di specifiche! –

+0

"La firma di un metodo non include il tipo di reso", Questa linea mi ha illuminato. – Gaddigesh

1

Non è inoltre necessario assegnare un valore a un metodo chiamato. Per esempio:

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

è possibile ottenere la sintassi hai mostrato in questione, ma è necessario impegnarsi in abbastanza una buona quantità di intelligenza per arrivarci.

Impostiamo il problema un po 'più concretamente. Il punto chiave qui, penso, è la sintassi. Vogliamo che questo:

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

Tuttavia, non vogliamo far funzionare tutto questo:

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

Al fine di raggiungere questo obiettivo, dobbiamo usare un oggetto intermediario il valore restituito da GetData(), la cui definizione assomiglia a questo:

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

La tua implementazione, ovviamente, sarebbe molto diversa, ma lo farà. Ora definiamo 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; 
    } 
} 

Ripetere questa definizione per T1, T2, T3, ecc per il caso generico.

È anche possibile associare l'helper del valore di ritorno molto vicino alla classe o al metodo che lo restituisce, consentendo di creare questo stesso tipo di effetto per gli indicizzatori, in cui è possibile ottenere e assegnare un insieme discreto di tipi. È lì che l'ho trovato più utile.

Un'altra applicazione è quello di consentire sintassi simile alla seguente:

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

L'esatto meccanismo per raggiungere questo sintassi è lasciato come esercizio al lettore.

Per una soluzione più generale a questo problema (che è, penso, lo stesso di un problema di unione discriminato), vedere my answer to that question.

+0

Avendo già trattato codice come questo: 'data [" Hello "] =" Mondo "; data ["infamy"] = new DateTime (1941, 12, 7); data [42] = "Vita, l'universo e tutto"; ' Non voglio più avere niente a che fare con esso. Se hai mai bisogno di una sintassi del genere, considera che stai facendo male e dovresti prendere in considerazione l'uso di un oggetto tipizzato. – aaronburro

Problemi correlati