Was ist besser, Rückgabewert oder Out-Parameter?


147

Wenn wir einen Wert von einer Methode erhalten möchten, können wir einen der folgenden Rückgabewerte verwenden:

public int GetValue(); 

oder:

public void GetValue(out int x);

Ich verstehe die Unterschiede zwischen ihnen nicht wirklich und weiß daher nicht, was besser ist. Kannst du mir das erklären?

Danke dir.


3
Ich wünschte, C # hätte mehrere Rückgabewerte wie zum Beispiel Python.
Falle

12
@Trap Sie können a zurückgeben, Tuplewenn Sie möchten, aber der allgemeine Konsens ist, dass, wenn Sie mehr als eine Sache zurückgeben müssen, die Dinge normalerweise irgendwie zusammenhängen und diese Beziehung normalerweise am besten als Klasse ausgedrückt wird.
Pharap

2
@Pharap Tupel in C # in ihrer aktuellen Form sind einfach hässlich, aber das ist nur meine Meinung. Auf der anderen Seite bedeutet "allgemeiner Konsens" unter dem Gesichtspunkt der Benutzerfreundlichkeit und Produktivität nichts. Sie würden keine Klasse für die Rückgabe einiger Werte erstellen, aus demselben Grund, aus dem Sie keine Klasse für die Rückgabe einiger Werte als ref / out-Parameter erstellen würden.
Falle

@Trap return Tuple.Create(x, y, z);Ist das nicht hässlich ? Außerdem ist es spät am Tag, sie auf Sprachniveau einzuführen. Der Grund, warum ich keine Klasse erstellen würde, um Werte aus einem ref / out-Parameter zurückzugeben, ist, dass ref / out-Parameter nur für große veränderbare Strukturen (wie Matrizen) oder optionale Werte wirklich Sinn machen und letzteres umstritten ist.
Pharap

Das @ Pharap C # -Team ist aktiv bemüht, Tupel auf Sprachebene einzuführen. Zwar ist die ganze Fülle an Optionen in .NET jetzt überwältigend - anonymer Typ, .NET- Tuple<>und C # -Tupel!. Ich wünschte nur, C # würde die Rückgabe anonymer Typen von Methoden mit vom Compiler abgeleitetem Typ autoerlauben (wie in Dlang).
Nawfal

Antworten:


153

Rückgabewerte sind fast immer die richtige Wahl, wenn die Methode nichts anderes zurückzugeben hat. (Tatsächlich kann ich mir keine Fälle vorstellen , in denen ich jemals eine void-Methode mit einem outParameter haben möchte , wenn ich die Wahl hätte. Die DeconstructMethoden von C # 7 für die sprachgestützte Dekonstruktion sind eine sehr, sehr seltene Ausnahme von dieser Regel .)

Abgesehen von allem anderen verhindert dies, dass der Aufrufer die Variable separat deklarieren muss:

int foo;
GetValue(out foo);

vs.

int foo = GetValue();

Out-Werte verhindern auch die Verkettung von Methoden wie folgt:

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

(In der Tat ist dies auch eines der Probleme bei Eigenschaftssetzern, und deshalb verwendet das Builder-Muster Methoden, die den Builder zurückgeben, z myStringBuilder.Append(xxx).Append(yyy).

Darüber hinaus sind Out-Parameter bei der Reflexion etwas schwieriger zu verwenden und erschweren normalerweise auch das Testen. (Normalerweise wird mehr Aufwand betrieben, um das Verspotten von Rückgabewerten zu vereinfachen als das Herausgeben von Parametern). Grundsätzlich fällt mir nichts ein, was sie einfacher machen ...

Rückgabewerte FTW.

EDIT: In Bezug auf was los ist ...

Grundsätzlich , wenn Sie in einem Argument für ein „out“ Parameter übergeben, Sie haben in einer Variable übergeben. (Array-Elemente werden auch als Variablen klassifiziert.) Die von Ihnen aufgerufene Methode hat keine "neue" Variable auf ihrem Stapel für den Parameter - sie verwendet Ihre Variable zum Speichern. Änderungen an der Variablen sind sofort sichtbar. Hier ist ein Beispiel, das den Unterschied zeigt:

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

Ergebnisse:

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

Der Unterschied liegt im Schritt "post" - dh nachdem die lokale Variable oder der lokale Parameter geändert wurde. Im ReturnValue-Test macht dies keinen Unterschied zur statischen valueVariablen. Im OutParameter-Test wird die valueVariable durch die Zeile geänderttmp = 10;


2
Du hast mich glauben lassen, dass der Rückgabewert viel besser ist :). Aber ich frage mich immer noch, was "in der Tiefe" passiert. Ich meine, der Rückgabewert und der out-Parameter unterscheiden sich in der Art und Weise, wie sie erstellt, zugewiesen und zurückgegeben werden.
Quan Mai

1
TryParse ist das beste Beispiel dafür, wenn die Verwendung eines out-Parameters angemessen und sauber ist. Obwohl ich es in besonderen Fällen wie if (WorkSucceeded (out List <string> -Fehler) verwendet habe, was im Grunde das gleiche Muster wie TryParse
Chad Grant

2
Eine arme, nicht hilfreiche Antwort. Wie in den Kommentaren zu dieser Frage erläutert , haben unsere Parameter mehrere nützliche Vorteile. Die Präferenz zwischen Out und Return sollte von der Situation abhängen.
Aaronsnoswell

2
@aaronsnoswell: Welche genauen Kommentare? Beachten Sie, dass das Beispiel Dictionary.TryGetValuehier nicht anwendbar ist, da dies keine nichtige Methode ist. Können Sie erklären, warum Sie einen outParameter anstelle eines Rückgabewerts wünschen ? (Auch für TryGetValue, ich würde einen Rückgabewert bevorzugen, der alle Ausgabeinformationen persönlich enthält. Siehe NodaTime ParseResult<T>für ein Beispiel, wie ich es entworfen hätte.)
Jon Skeet

2
@ Jim: Ich denke, wir müssen zustimmen, nicht zuzustimmen. Ich sehe zwar Ihren Standpunkt, Menschen damit über den Kopf zu hämmern, aber es macht es auch weniger freundlich für diejenigen, die wissen, was sie tun. Das Schöne an einer Ausnahme und nicht an einem Rückgabewert ist, dass Sie sie nicht einfach ignorieren und so weitermachen können, als wäre nichts passiert ... während Sie sowohl mit einem Rückgabewert als auch mit einem outParameter nichts mit dem Wert anfangen können.
Jon Skeet

26

Was besser ist, hängt von Ihrer speziellen Situation ab. Einer der Gründe outbesteht darin, die Rückgabe mehrerer Werte aus einem Methodenaufruf zu erleichtern:

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

    return input;
}

Das eine ist also per Definition nicht besser als das andere. Normalerweise möchten Sie jedoch eine einfache Rückgabe verwenden, es sei denn, Sie haben beispielsweise die oben genannte Situation.

BEARBEITEN: Dies ist ein Beispiel, das einen der Gründe für das Vorhandensein des Schlüsselworts zeigt. Das oben Gesagte kann in keiner Weise als Best Practice angesehen werden.


2
Ich bin damit nicht einverstanden. Warum würden Sie keine Datenstruktur mit 4 Zoll zurückgeben? Das ist wirklich verwirrend.
Chad Grant

1
Offensichtlich gibt es mehr (und bessere) Möglichkeiten, mehrere Werte zurückzugeben. Ich gebe dem OP nur einen Grund, warum out überhaupt existiert.
Pyrocumulus

1
Ich stimme @Cloud zu. Nur weil es nicht der beste Weg ist, heißt das nicht, dass es nicht existieren sollte.
Cerebrus

Nun, der Code wird nicht einmal für einen kompiliert ... und es sollte zumindest erwähnt werden, dass er als schlechte Praxis angesehen wird / nicht bevorzugt wird.
Chad Grant

1
Es wird nicht kompiliert? Und warum ist das? Dieses Beispiel könnte einfach perfekt kompiliert werden. Und bitte, ich gebe ein Beispiel dafür, warum eine bestimmte Syntax / ein bestimmtes Schlüsselwort existiert, und keine Lektion in Best Practices.
Pyrocumulus

23

Sie sollten im Allgemeinen einen Rückgabewert einem out-Parameter vorziehen. Unsere Parameter sind ein notwendiges Übel, wenn Sie Code schreiben, der zwei Dinge tun muss. Ein gutes Beispiel hierfür ist das Try-Muster (z. B. Int32.TryParse).

Lassen Sie uns überlegen, was der Aufrufer Ihrer beiden Methoden tun müsste. Für das erste Beispiel kann ich das schreiben ...

int foo = GetValue();

Beachten Sie, dass ich eine Variable deklarieren und über Ihre Methode in einer Zeile zuweisen kann. Für das 2. Beispiel sieht es so aus ...

int foo;
GetValue(out foo);

Ich bin jetzt gezwungen, meine Variable im Voraus zu deklarieren und meinen Code über zwei Zeilen zu schreiben.

aktualisieren

Ein guter Ort, um diese Art von Fragen zu stellen, sind die .NET Framework-Entwurfsrichtlinien. Wenn Sie die Buchversion haben, können Sie die Anmerkungen von Anders Hejlsberg und anderen zu diesem Thema sehen (Seite 184-185), aber die Online-Version ist hier ...

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

Wenn Sie zwei Dinge von einer API zurückgeben müssen, ist es besser, sie in eine Struktur / Klasse einzupacken, als einen out-Parameter.


Gute Antwort, insbesondere der Verweis auf TryParse, eine (allgemeine) Funktion, die Entwickler dazu zwingt, die (ungewöhnlichen) Out-Variablen zu verwenden.
Cerebrus

12

Es gibt einen Grund, einen outParameter zu verwenden, der noch nicht erwähnt wurde: Die aufrufende Methode ist verpflichtet, ihn zu empfangen. Wenn Ihre Methode einen Wert erzeugt, den der Aufrufer nicht verwerfen sollte, wird der Aufrufer gezwungen, ihn outausdrücklich zu akzeptieren:

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

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

Natürlich kann der Aufrufer den Wert in einem outParameter immer noch ignorieren , aber Sie haben seine Aufmerksamkeit darauf gelenkt.

Dies ist ein seltener Bedarf; häufiger sollten Sie eine Ausnahme für ein echtes Problem verwenden oder ein Objekt mit Statusinformationen für eine "FYI" zurückgeben, aber es kann Umstände geben, unter denen dies wichtig ist.


8

Es ist hauptsächlich die Präferenz

Ich bevorzuge Retouren und wenn Sie mehrere Retouren haben, können Sie diese in ein Ergebnis-DTO einschließen

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

5

Sie können nur einen Rückgabewert haben, während Sie mehrere Out-Parameter haben können.

Sie müssen nur in diesen Fällen Parameter berücksichtigen.

Wenn Sie jedoch mehr als einen Parameter von Ihrer Methode zurückgeben müssen, möchten Sie sich wahrscheinlich ansehen, was Sie von einem OO-Ansatz zurückgeben, und überlegen, ob Sie ein Objekt oder eine Struktur mit diesen Parametern besser zurückgeben sollten. Daher kehren Sie wieder zu einem Rückgabewert zurück.


5

Sie sollten fast immer einen Rückgabewert verwenden. ' out' Parameter verursachen ein wenig Reibung bei vielen APIs, der Komposition usw.

Die bemerkenswerteste Ausnahme, die Ihnen in den Sinn kommt, ist, wenn Sie mehrere Werte zurückgeben möchten (.Net Framework hat bis 4.0 keine Tupel), z. B. mit dem TryParseMuster.


Ich bin mir nicht sicher, ob dies eine gute Vorgehensweise ist, aber Arraylist kann auch verwendet werden, um mehrere Werte zurückzugeben.
BA

@BA nein, es ist keine gute Praxis, andere Benutzer würden nicht wissen, was diese Arrayliste enthält oder an welcher Position sie sich befinden. Zum Beispiel möchte ich die Anzahl der gelesenen Bytes und auch den Wert zurückgeben. out oder Tupel, die benannt sind, wären viel besser. ArrayList oder eine andere Liste ist nur dann nützlich, wenn Sie eine Kollaktion von Dingen zurückgeben möchten, beispielsweise eine Liste von Personen.
SLL

2

Ich würde das Folgende anstelle eines der beiden in diesem einfachen Beispiel bevorzugen.

public int Value
{
    get;
    private set;
}

Aber sie sind alle sehr ähnlich. Normalerweise würde man 'out' nur verwenden, wenn mehrere Werte von der Methode zurückgegeben werden müssen. Wenn Sie einen Wert in und aus der Methode senden möchten, wählen Sie 'ref'. Meine Methode ist am besten, wenn Sie nur einen Wert zurückgeben, aber wenn Sie einen Parameter übergeben und einen Wert zurückerhalten möchten, wählen Sie wahrscheinlich Ihre erste Wahl.


2

Ich denke, eines der wenigen Szenarien, in denen es nützlich wäre, wäre die Arbeit mit nicht verwaltetem Speicher, und Sie möchten klarstellen, dass der "zurückgegebene" Wert manuell entsorgt werden sollte, anstatt zu erwarten, dass er selbst entsorgt wird .


2

Darüber hinaus sind Rückgabewerte mit asynchronen Entwurfsparadigmen kompatibel.

Sie können eine Funktion nicht als "asynchron" festlegen, wenn sie ref- oder out-Parameter verwendet.

Zusammenfassend lässt sich sagen, dass Rückgabewerte eine Verkettung von Methoden und eine sauberere Syntax ermöglichen (da der Aufrufer keine zusätzlichen Variablen mehr deklarieren muss) und asynchrone Designs ermöglichen, ohne dass in Zukunft wesentliche Änderungen erforderlich sind.


Toller Punkt auf der Bezeichnung "async". Ich dachte, jemand anderes hätte das erwähnt. Verketten - sowie die Verwendung des Rückgabewerts (in der Tat der Funktion selbst) als Ausdruck ist ein weiterer entscheidender Vorteil. Es ist wirklich der Unterschied zwischen der Überlegung, wie man nur Dinge aus einem Prozess zurückholt (der "Bucket" - Tupel / Klasse / Struktur-Diskussion) und der Behandlung des Prozesses selbst wie ein Ausdruck, der einen einzelnen Wert ersetzen kann (die Nützlichkeit des Funktion selbst, da nur 1 Wert zurückgegeben wird).
user1172173

1

Beide haben einen unterschiedlichen Zweck und werden vom Compiler nicht gleich behandelt. Wenn Ihre Methode einen Wert zurückgeben muss, müssen Sie return verwenden. Out wird verwendet, wenn Ihre Methode mehrere Werte zurückgeben muss.

Wenn Sie return verwenden, werden die Daten zuerst in den Methodenstapel und dann in die aufrufenden Methoden geschrieben. Im Falle von out wird es direkt in den aufrufenden Methodenstapel geschrieben. Ich bin mir nicht sicher, ob es weitere Unterschiede gibt.


Die Methoden stapeln? Ich bin kein C # -Experte, aber x86 unterstützt nur einen Stapel pro Thread. Der "Frame" der Methode wird während der Rückgabe freigegeben. Wenn ein Kontextwechsel stattgefunden hat, kann der freigegebene Stapel überschrieben werden. In c gehen alle Rückgabewerte in die Registrierung eax. Wenn Sie Objekte / Strukturen zurückgeben möchten, müssen diese dem Heap zugewiesen werden, und der Zeiger wird in eax gesetzt.
Stefan Lundström

1

Wie andere gesagt haben: Rückgabewert, nicht out param.

Darf ich Ihnen das Buch "Framework Design Guidelines" (2. Aufl.) Empfehlen? Die Seiten 184-185 behandeln die Gründe für das Vermeiden von Parametern. Das ganze Buch wird Sie bei allen Arten von .NET-Codierungsproblemen in die richtige Richtung lenken.

Verbunden mit den Framework Design Guidelines ist die Verwendung des statischen Analysetools FxCop. Sie finden dies auf den Microsoft-Websites als kostenlosen Download. Führen Sie dies auf Ihrem kompilierten Code aus und sehen Sie, was darin steht. Wenn es sich über Hunderte und Hunderte von Dingen beschwert ... keine Panik! Schauen Sie sich ruhig und genau an, was in jedem Fall steht. Beeilen Sie sich nicht, um Dinge so schnell wie möglich zu reparieren. Lerne aus dem, was es dir sagt. Sie werden auf den Weg zur Meisterschaft gebracht.


1

Die Verwendung des Schlüsselworts out mit einem Rückgabetyp von bool kann manchmal das Aufblähen des Codes verringern und die Lesbarkeit verbessern. (In erster Linie, wenn die zusätzlichen Informationen im Parameter out häufig ignoriert werden.) Zum Beispiel:

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

Es gibt keinen wirklichen Unterschied. Unsere Parameter sind in C # angegeben, damit die Methode mehr als einen Wert zurückgeben kann. Das ist alles.

Es gibt jedoch einige geringfügige Unterschiede, von denen jedoch keine wirklich wichtig sind:

Wenn Sie den Parameter out verwenden, müssen Sie zwei Zeilen verwenden:

int n;
GetValue(n);

Wenn Sie den Rückgabewert verwenden, können Sie dies in einer Zeile tun:

int n = GetValue();

Ein weiterer Unterschied (nur für Werttypen korrekt und nur dann, wenn C # die Funktion nicht integriert) besteht darin, dass bei Verwendung der Rückgabewert bei der Rückgabe der Funktion unbedingt eine Kopie des Werts erstellt wird, bei Verwendung des Parameters OUT nicht unbedingt.


0

out ist nützlicher, wenn Sie versuchen, ein Objekt zurückzugeben, das Sie in der Methode deklariert haben.

Beispiel

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

Rückgabewert ist der normale Wert, der von Ihrer Methode zurückgegeben wird.

Wo als out- Parameter, well out und ref zwei Schlüsselwörter von C # sind, können Variablen als Referenz übergeben werden .

Der große Unterschied zwischen ref und out ist, ref sollte vor und initialisiert wurde aus nicht


-2

Ich vermute, ich werde mich nicht mit dieser Frage befassen, aber ich bin ein sehr erfahrener Programmierer, und ich hoffe, dass einige der aufgeschlosseneren Leser darauf achten werden.

Ich glaube, dass es für objektorientierte Programmiersprachen besser geeignet ist, wenn ihre Value-Returning-Verfahren (VRPs) deterministisch und rein sind.

'VRP' ist der moderne akademische Name für eine Funktion, die als Teil eines Ausdrucks aufgerufen wird und einen Rückgabewert hat, der den Aufruf während der Auswertung des Ausdrucks fiktiv ersetzt. ZB in einer Anweisung wie x = 1 + f(y)der Funktion fdient die Funktion als VRP.

'Deterministisch' bedeutet, dass das Ergebnis der Funktion nur von den Werten ihrer Parameter abhängt. Wenn Sie es erneut mit denselben Parameterwerten aufrufen, erhalten Sie mit Sicherheit dasselbe Ergebnis.

'Rein' bedeutet keine Nebenwirkungen: Der Aufruf der Funktion bewirkt nichts anderes als die Berechnung des Ergebnisses. Dies kann so interpretiert werden, dass es in der Praxis keine wichtigen Nebenwirkungen gibt. Wenn der VRP beispielsweise bei jedem Aufruf eine Debugging-Nachricht ausgibt, kann dies wahrscheinlich ignoriert werden.

Wenn Ihre Funktion in C # nicht deterministisch und rein ist, sollten Sie sie zu einer voidFunktion machen (mit anderen Worten, nicht zu einem VRP), und jeder Wert, den sie zurückgeben muss, sollte entweder in einem outoder einem refParameter zurückgegeben werden.

Wenn Sie beispielsweise eine Funktion zum Löschen einiger Zeilen aus einer Datenbanktabelle haben und möchten, dass diese die Anzahl der gelöschten Zeilen zurückgibt, sollten Sie Folgendes deklarieren:

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

Wenn Sie diese Funktion manchmal aufrufen möchten, aber die nicht erhalten möchten count, können Sie jederzeit eine Überladung deklarieren.

Vielleicht möchten Sie wissen, warum dieser Stil besser zur objektorientierten Programmierung passt. Im Großen und Ganzen passt es in einen Programmierstil, der (etwas ungenau) als "prozedurale Programmierung" bezeichnet werden könnte, und es ist ein prozeduraler Programmierstil, der besser zur objektorientierten Programmierung passt.

Warum? Das klassische Modell von Objekten besteht darin, dass sie Eigenschaften (auch Attribute genannt) haben und Sie das Objekt (hauptsächlich) abfragen und bearbeiten, indem Sie diese Eigenschaften lesen und aktualisieren. Ein prozeduraler Programmierstil erleichtert dies in der Regel, da Sie zwischen Operationen, die Eigenschaften abrufen und festlegen, beliebigen Code ausführen können.

Der Nachteil der prozeduralen Programmierung besteht darin, dass Sie, da Sie überall beliebigen Code ausführen können, über globale Variablen und Nebenwirkungen einige sehr stumpfe und fehleranfällige Interaktionen erhalten können.

Es ist also ganz einfach eine gute Praxis, jemandem, der Ihren Code liest, zu signalisieren , dass eine Funktion Nebenwirkungen haben kann, indem sie nicht wertmäßig zurückgegeben wird.


> Wenn Sie diese Funktion manchmal aufrufen möchten, aber nicht die Anzahl erhalten möchten, können Sie jederzeit eine Überladung deklarieren. In C # Version 7 (glaube ich) und höher können Sie das _Discard-Symbol verwenden, um einen out-Parameter zu ignorieren, z. B.: DeleteBasketItems (category, out _);
Debater
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.