Quale è meglio, restituisce valore o parametro out?


147

Se vogliamo ottenere un valore da un metodo, possiamo usare entrambi i valori di ritorno, in questo modo:

public int GetValue(); 

o:

public void GetValue(out int x);

Non capisco davvero le differenze tra loro e quindi non so quale sia la migliore. Puoi spiegarmi questo?

Grazie.


3
Vorrei che C # avesse più valori di ritorno come Python, per esempio.
Trappola

12
@Trap Puoi restituire un Tuplese vuoi, ma il consenso generale è che se hai bisogno di restituire più di una cosa, le cose sono di solito correlate in qualche modo e quella relazione è di solito meglio espressa come una classe.
Pharap,

2
@Pharap Tuples in C # nella sua forma attuale sono semplicemente brutti, ma questa è solo la mia opinione. D'altro canto, "consenso generale" non significa nulla dal punto di vista dell'usabilità e della produttività. Non creeresti una classe per restituire un paio di valori per lo stesso motivo per cui non creeresti una classe per restituire un paio di valori come parametro ref / out.
Trappola

@Trap return Tuple.Create(x, y, z);Non è così brutto. Inoltre, è tardi per averli introdotti a livello linguistico. Il motivo per cui non creerei una classe per restituire valori da un parametro ref / out è perché i parametri ref / out hanno davvero senso solo per grandi strutture mutabili (come matrici) o valori opzionali, e quest'ultimo è discutibile.
Pharap,

Il team di @Pharap C # sta attivamente cercando di introdurre tuple a livello linguistico. Mentre è benvenuta l'intera pletora di opzioni in .NET ora travolgente - tipo anonimo, Tuple<>tuple .NET e C # !. Ho solo desiderato che C # consentisse il ritorno di tipi anonimi da metodi con compilatore che ne deduceva il tipo (come autoin Dlang).
nawfal,

Risposte:


153

I valori restituiti sono quasi sempre la scelta giusta quando il metodo non ha nient'altro da restituire. (In effetti, non posso pensare a nessun caso in cui avrei mai desiderato un metodo vuoto con un outparametro, se ne avessi avuto la possibilità. I Deconstructmetodi di C # 7 per la decostruzione supportata dal linguaggio agiscono come un'eccezione molto, molto rara a questa regola .)

A parte tutto, impedisce al chiamante di dichiarare la variabile separatamente:

int foo;
GetValue(out foo);

vs

int foo = GetValue();

I valori out impediscono anche il concatenamento dei metodi in questo modo:

Console.WriteLine(GetValue().ToString("g"));

(In effetti, questo è uno dei problemi anche con i setter di proprietà, ed è il motivo per cui il modello builder utilizza metodi che restituiscono il builder, ad es myStringBuilder.Append(xxx).Append(yyy).)

Inoltre, i parametri out sono leggermente più difficili da usare con la riflessione e di solito rendono anche più difficili i test. (Di solito viene fatto uno sforzo maggiore per rendere più semplice la derisione dei valori di ritorno rispetto ai parametri di uscita). Fondamentalmente non riesco a pensare che rendano più facile ...

Valori di ritorno FTW.

EDIT: in termini di ciò che sta succedendo ...

Fondamentalmente quando si passa un argomento per un parametro "out", è necessario passare una variabile. (Anche gli elementi dell'array sono classificati come variabili.) Il metodo chiamato non ha una "nuova" variabile nel suo stack per il parametro: utilizza la variabile per l'archiviazione. Eventuali modifiche alla variabile sono immediatamente visibili. Ecco un esempio che mostra la differenza:

using System;

class Test
{
    static int value;

    static void ShowValue(string description)
    {
        Console.WriteLine(description + value);
    }

    static void Main()
    {
        Console.WriteLine("Return value test...");
        value = 5;
        value = ReturnValue();
        ShowValue("Value after ReturnValue(): ");

        value = 5;
        Console.WriteLine("Out parameter test...");
        OutParameter(out value);
        ShowValue("Value after OutParameter(): ");
    }

    static int ReturnValue()
    {
        ShowValue("ReturnValue (pre): ");
        int tmp = 10;
        ShowValue("ReturnValue (post): ");
        return tmp;
    }

    static void OutParameter(out int tmp)
    {
        ShowValue("OutParameter (pre): ");
        tmp = 10;
        ShowValue("OutParameter (post): ");
    }
}

risultati:

Return value test...
ReturnValue (pre): 5
ReturnValue (post): 5
Value after ReturnValue(): 10
Out parameter test...
OutParameter (pre): 5
OutParameter (post): 10
Value after OutParameter(): 10

La differenza è nel passaggio "post", ovvero dopo che la variabile locale o il parametro è stato modificato. Nel test ReturnValue, questo non fa differenza per la valuevariabile statica . Nel test OutParameter, la valuevariabile viene modificata dalla rigatmp = 10;


2
Mi hai fatto credere che il valore di ritorno sia molto meglio :). Ma mi chiedo ancora cosa succede "in profondità". Voglio dire, il valore restituito e il parametro out sono diversi nel modo in cui vengono creati, assegnati e restituiti?
Quan Mai,

1
TryParse è il miglior esempio di quando si utilizza un parametro out è appropriato e pulito. Anche se l'ho usato in casi speciali come if (WorkSucceeded (out List <string> errori) che è sostanzialmente lo stesso modello di TryParse
Chad Grant

2
Un povero una risposta inutile. Come spiegato nei commenti su questa domanda , i parametri out presentano numerosi vantaggi utili. La preferenza tra out e return dovrebbe dipendere dalla situazione.
aaronsnoswell,

2
@aaronsnoswell: quali commenti precisi? Tieni presente che l'esempio di nonDictionary.TryGetValue è applicabile qui, in quanto non è un metodo nullo. Puoi spiegare perché vorresti un parametro invece di un valore di ritorno? (Anche per , preferirei un valore di ritorno che contenga tutte le informazioni di output, personalmente. Vedi NodaTime per un esempio di come l'avrei progettato.)outTryGetValueParseResult<T>
Jon Skeet il

2
@Jim: immagino che dovremo essere in disaccordo. Mentre vedo il tuo punto circa martellare le persone sopra la testa con questo, ma rende anche meno amichevole per coloro che non sanno quello che stanno facendo. La cosa bella di un'eccezione piuttosto che di un valore di ritorno è che non puoi facilmente ignorarlo e continuare come se nulla fosse accaduto ... mentre con un valore di ritorno e un outparametro non puoi semplicemente fare nulla con il valore.
Jon Skeet,

26

Cosa c'è di meglio, dipende dalla tua situazione particolare. Uno dei motivi outesiste è facilitare la restituzione di più valori da una chiamata di metodo:

public int ReturnMultiple(int input, out int output1, out int output2)
{
    output1 = input + 1;
    output2 = input + 2;

    return input;
}

Quindi uno non è per definizione migliore dell'altro. Ma di solito vorresti usare un semplice ritorno, a meno che tu non abbia la situazione sopra per esempio.

EDIT: questo è un esempio che dimostra uno dei motivi per cui esiste la parola chiave. Quanto sopra non è in alcun modo da considerare una migliore pratica.


2
Non sono d'accordo con questo, perché non dovresti restituire una struttura di dati con 4 pollici? Questo è davvero confuso.
Chad Grant,

1
Ovviamente ci sono più (e migliori) modi per restituire più valori, sto solo dando al PO un motivo per cui esiste in primo luogo.
pyrocumulus,

2
Sono d'accordo con @Cloud. Solo perché non è il modo migliore non significa che non dovrebbe esistere.
Cerebrus,

Beh, il codice non si compila nemmeno per uno ... e dovrebbe almeno menzionare che è considerato una cattiva pratica / non preferita.
Chad Grant,

1
Non si compila? E perché? Questo esempio potrebbe compilare semplicemente perfetto. E per favore, sto dando un esempio del perché esiste una sintassi / parola chiave particolare, non una lezione sulle migliori pratiche.
pyrocumulus,

23

In genere dovresti preferire un valore di ritorno a un parametro out. I nostri parametri sono un male necessario se ti ritrovi a scrivere codice che deve fare 2 cose. Un buon esempio di ciò è il modello Try (come Int32.TryParse).

Consideriamo cosa dovrebbe fare il chiamante dei tuoi due metodi. Per il primo esempio posso scrivere questo ...

int foo = GetValue();

Notare che posso dichiarare una variabile e assegnarla tramite il metodo in una riga. Per il secondo esempio sembra così ...

int foo;
GetValue(out foo);

Ora sono costretto a dichiarare la mia variabile in anticipo e scrivere il mio codice su due righe.

aggiornare

Un buon posto in cui cercare quando si pongono questi tipi di domande sono le Linee guida per la progettazione di .NET Framework. Se hai la versione del libro, puoi vedere le annotazioni di Anders Hejlsberg e altri su questo argomento (pagina 184-185) ma la versione online è qui ...

http://msdn.microsoft.com/en-us/library/ms182131(VS.80).aspx

Se ti ritrovi a dover restituire due cose da un'API, avvolgerle in una struttura / classe sarebbe meglio di un parametro fuori.


Ottima risposta, in particolare il riferimento a TryParse, una funzione (comune) che obbliga gli sviluppatori a utilizzare le variabili (non comuni) out.
Cerebrus,

12

C'è un motivo per usare un outparametro che non è già stato menzionato: il metodo chiamante è obbligato a riceverlo. Se il tuo metodo produce un valore che il chiamante non dovrebbe scartare, rendendolo un outforzante per accettarlo specificamente:

 Method1();  // Return values can be discard quite easily, even accidentally

 int  resultCode;
 Method2(out resultCode);  // Out params are a little harder to ignore

Naturalmente il chiamante può ancora ignorare il valore in un outparametro, ma tu hai richiamato la sua attenzione su di esso.

Questo è un bisogno raro; più spesso, è necessario utilizzare un'eccezione per un problema reale o restituire un oggetto con informazioni sullo stato per un "FYI", ma potrebbero esserci circostanze in cui questo è importante.


8

È principalmente la preferenza

Preferisco i ritorni e se hai più ritorni puoi avvolgerli in un DTO risultato

public class Result{
  public Person Person {get;set;}
  public int Sum {get;set;}
}

5

Puoi avere un solo valore di ritorno mentre puoi avere più parametri di uscita.

In questi casi è necessario considerare solo i parametri.

Tuttavia, se è necessario restituire più di un parametro dal metodo, probabilmente si desidera esaminare ciò che si sta tornando da un approccio OO e considerare se è meglio restituire un oggetto o una struttura con questi parametri. Pertanto, si ritorna nuovamente a un valore di ritorno.


5

Dovresti quasi sempre usare un valore di ritorno. ' out' i parametri creano un po 'di attrito per molte API, composizionalità, ecc.

L'eccezione più degna di nota che viene in mente è quando si desidera restituire più valori (.Net Framework non ha tuple fino alla 4.0), come nel caso del TryParsemodello.


Non sono sicuro che sia una buona pratica ma Arraylist può anche essere usato per restituire più valori.
BA,

@BA no non è una buona pratica, gli altri utenti non saprebbero cosa contiene questo Arraylist o in quale posizione si trovano. Ad esempio, voglio restituire la quantità di byte letti e anche il valore. fuori o tuple che sono nominati sarebbe molto meglio. ArrayList o qualsiasi altro elenco sarebbe utile solo se si desidera restituire una collisione di cose, ad esempio un elenco di persone.
sabato

2

Preferirei quanto segue anziché uno di quelli in questo semplice esempio.

public int Value
{
    get;
    private set;
}

Ma sono tutti uguali. Di solito, si userebbe 'out' solo se è necessario restituire più valori dal metodo. Se si desidera inviare un valore dentro e fuori dal metodo, si sceglierebbe 'ref'. Il mio metodo è il migliore, se stai solo restituendo un valore, ma se vuoi passare un parametro e recuperare un valore, uno probabilmente sceglierebbe la tua prima scelta.


2

Penso che uno dei pochi scenari in cui sarebbe utile sarebbe quando si lavora con memoria non gestita e si desidera rendere ovvio che il valore "restituito" dovrebbe essere eliminato manualmente, anziché aspettarsi che venga eliminato da solo .


2

Inoltre, i valori di ritorno sono compatibili con i paradigmi di progettazione asincroni.

Non è possibile designare una funzione "asincrona" se utilizza parametri ref o out.

In breve, i valori di ritorno consentono il concatenamento dei metodi, una sintassi più pulita (eliminando la necessità per il chiamante di dichiarare ulteriori variabili) e consentono la progettazione asincrona senza la necessità di modifiche sostanziali in futuro.


Ottimo punto sulla designazione "asincrona". Pensavo che qualcun altro l'avrebbe menzionato. Concatenamento - oltre a utilizzare il valore restituito (in effetti la funzione stessa) come espressione è un altro vantaggio chiave. È davvero la differenza tra considerare solo come recuperare le cose da un processo (la discussione "bucket" - Tuple / class / struct) e trattare il processo stesso come un'espressione che può essere sostituita per un singolo valore (l'utilità del stessa funzione, perché restituisce solo 1 valore).
user1172173

1

Entrambi hanno uno scopo diverso e non sono trattati allo stesso modo dal compilatore. Se il metodo deve restituire un valore, è necessario utilizzare return. Out viene utilizzato dove il metodo deve restituire più valori.

Se si utilizza return, i dati vengono prima scritti nello stack dei metodi e poi in quelli del metodo chiamante. Mentre in caso di out, viene scritto direttamente nello stack dei metodi di chiamata. Non sono sicuro se ci siano altre differenze.


I metodi si accumulano? Non sono un esperto di c #, ma x86 supporta solo uno stack per thread. Il "frame" del metodo viene deallocato durante il ritorno, e se un cambio di contesto avviene, lo stack deallocato può essere sovrascritto. In c tutti i valori di ritorno vanno nel eax del registro. Se si desidera restituire oggetti / strutture, è necessario assegnarli all'heap e il puntatore verrà inserito in eax.
Stefan Lundström,

1

Come altri hanno già detto: restituisce valore, non fuori parametro.

Posso consigliarvi il libro "Framework Design Guidelines" (2a edizione)? Le pagine 184-185 illustrano le ragioni per evitare i parametri. L'intero libro ti guiderà nella giusta direzione su tutti i tipi di problemi di codifica .NET.

Insieme alle linee guida per la progettazione del framework è l'uso dello strumento di analisi statica, FxCop. Lo troverai sui siti di Microsoft come download gratuito. Esegui questo sul tuo codice compilato e vedi cosa dice. Se si lamenta di centinaia e centinaia di cose ... non fatevi prendere dal panico! Osserva con calma e attenzione ciò che dice su ogni singolo caso. Non abbiate fretta di riparare le cose al più presto. Impara da cosa ti sta dicendo. Sarai messo sulla strada della maestria.


1

L'uso della parola chiave out con un tipo di ritorno bool, a volte può ridurre il gonfiore del codice e aumentare la leggibilità. (Principalmente quando le informazioni extra nel parametro out sono spesso ignorate.) Ad esempio:

var result = DoThing();
if (result.Success)
{
    result = DoOtherThing()
    if (result.Success)
    {
        result = DoFinalThing()
        if (result.Success)
        {
            success = true;
        }
    }
}

vs:

var result;
if (DoThing(out result))
{
    if (DoOtherThing(out result))
    {
        if (DoFinalThing(out result))
        {
            success = true;
        }
    }
}

1

Non c'è vera differenza. I parametri Out sono in C # per consentire al metodo di restituire più di un valore, tutto qui.

Tuttavia ci sono alcune lievi differenze, ma nessuna di queste è davvero importante:

L'uso del parametro out ti impone di utilizzare due righe come:

int n;
GetValue(n);

mentre usando il valore di ritorno ti permetterà di farlo in una riga:

int n = GetValue();

Un'altra differenza (corretta solo per i tipi di valore e solo se C # non incorpora la funzione) è che l'uso del valore restituito farà necessariamente una copia del valore quando la funzione restituisce, mentre l'uso del parametro OUT non lo farà necessariamente.


0

out è più utile quando si tenta di restituire un oggetto dichiarato nel metodo.

Esempio

public BookList Find(string key)
{
   BookList book; //BookList is a model class
   _books.TryGetValue(key, out book) //_books is a concurrent dictionary
                                     //TryGetValue gets an item with matching key and returns it into book.
   return book;
}

0

Il valore restituito è il valore normale restituito dal metodo.

Dove come parametro out , well out e ref sono 2 parole chiave di C # permettono di passare variabili come riferimento .

La grande differenza tra ref e out è che ref dovrebbe essere inizializzato prima e out no


-2

Ho il sospetto che non darò un'occhiata a questa domanda, ma sono un programmatore molto esperto e spero che alcuni dei lettori più aperti presteranno attenzione.

Credo che si adatti meglio ai linguaggi di programmazione orientati agli oggetti perché le loro procedure di ritorno del valore (VRP) siano deterministiche e pure.

'VRP' è il nome accademico moderno per una funzione chiamata come parte di un'espressione e ha un valore di ritorno che sostituisce teoricamente la chiamata durante la valutazione dell'espressione. Ad esempio in una dichiarazione come x = 1 + f(y)la funzione ffunge da VRP.

"Deterministico" significa che il risultato della funzione dipende solo dai valori dei suoi parametri. Se lo si chiama di nuovo con gli stessi valori dei parametri, si è certi di ottenere lo stesso risultato.

Significa 'puro' effetti collaterali: la chiamata alla funzione non fa nulla , tranne il calcolo del risultato. Questo può essere interpretato per non significare effetti collaterali importanti , in pratica, quindi se il VRP emette un messaggio di debug ogni volta che viene chiamato, ad esempio, probabilmente può essere ignorato.

Pertanto, se, in C #, la tua funzione non è deterministica e pura, dico che dovresti renderla una voidfunzione (in altre parole, non un VRP) e qualsiasi valore che deve restituire dovrebbe essere restituito in uno outo in un refparametro.

Ad esempio, se si dispone di una funzione per eliminare alcune righe da una tabella del database e si desidera che restituisca il numero di righe che è stato eliminato, è necessario dichiararlo in questo modo:

public void DeleteBasketItems(BasketItemCategory category, out int count);

Se a volte vuoi chiamare questa funzione ma non ottenere il count, puoi sempre dichiarare un sovraccarico.

Potresti voler sapere perché questo stile si adatta meglio alla programmazione orientata agli oggetti. In linea di massima, si adatta a uno stile di programmazione che potrebbe essere (un po 'imprecisamente) definito "programmazione procedurale", ed è uno stile di programmazione procedurale che si adatta meglio alla programmazione orientata agli oggetti.

Perché? Il modello classico di oggetti è che hanno proprietà (alias attributi) e tu interroghi e manipoli l'oggetto (principalmente) attraverso la lettura e l'aggiornamento di tali proprietà. Uno stile di programmazione procedurale tende a semplificare tale operazione, poiché è possibile eseguire codice arbitrario tra operazioni che ottengono e impostano proprietà.

L'aspetto negativo della programmazione procedurale è che, poiché è possibile eseguire codice arbitrario ovunque, è possibile ottenere alcune interazioni molto ottuse e vulnerabili ai bug tramite variabili globali ed effetti collaterali.

Quindi, in parole povere, è buona norma segnalare a qualcuno che legge il tuo codice che una funzione potrebbe avere effetti collaterali rendendola senza valore.


> Se a volte si desidera chiamare questa funzione ma non si ottiene il conteggio, è sempre possibile dichiarare un sovraccarico. In C # versione 7 (credo) e versioni successive, è possibile utilizzare il _simbolo discard per ignorare un parametro out, ad esempio: DeleteBasketItems (categoria, out _);
dibattente il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.