戻り値と出力パラメーターのどちらが優れていますか?


147

メソッドから値を取得する場合は、次のようにいずれかの戻り値を使用できます。

public int GetValue(); 

または:

public void GetValue(out int x);

両者の違いがよくわからないので、どちらがいいのかわかりません。説明してくれませんか?

ありがとうございました。


3
C#には、たとえばPythonのような複数の戻り値が必要です。
2015

12
@Trap必要に応じてaを返すことができTupleますが、一般的なコンセンサスは、複数のものを返す必要がある場合、それらは通常何らかの形で関連しており、その関係は通常クラスとして最もよく表現されるということです。
Pharap

2
現在の形式のC#の@Pharapタプルは見苦しいだけですが、それは私の意見です。一方、「一般的なコンセンサス」は、ユーザビリティと生産性の観点からは何も意味しません。ref / outパラメータとして2つの値を返すクラスを作成しないのと同じ理由で、2つの値を返すクラスを作成しません。
トラップ

@Trap return Tuple.Create(x, y, z);醜いじゃないですか。その上、言語レベルでそれらを紹介してもらうのは、今日は遅いです。ref / outパラメーターから値を返すクラスを作成しないのは、ref / outパラメーターは実際には大きな可変構造体(マトリックスなど)またはオプションの値に対してのみ意味があり、後者は議論の余地があるためです。
Pharap、2016年

@Pharap C#チームは、言語レベルでのタプルの導入に積極的に取り組んでいます。それは歓迎されますが、今では圧倒的な.NETの膨大なオプション-匿名タイプ、.NET Tuple<>およびC#タプル!C#が、(autoDlangのように)コンパイラーが型を推測するメソッドから匿名型を返すことを許可したかっただけです。
nawfal

回答:


153

メソッドが返すものがない場合、戻り値はほとんど常に正しい選択です。(実際に、私は私がしたい任意の例を考えることができない、これまでに、ボイドの方法をしたいout、私は選択肢を持っていた場合は、パラメータ。7のC#Deconstruct言語サポート解体のための方法は、このルールには非常に、非常にまれな例外として働き。)

それ以外は、呼び出し元が変数を個別に宣言する必要がなくなります。

int foo;
GetValue(out foo);

int foo = GetValue();

out値は、次のようなメソッドチェーンも防止します。

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

(確かに、これはプロパティセッターの問題の1つでもあり、ビルダーパターンがビルダーを返すメソッドを使用するのはそのためmyStringBuilder.Append(xxx).Append(yyy)です。例:)

さらに、出力パラメーターはリフレクションで使用するのが少し難しく、通常はテストも難しくなります。(通常、出力パラメーターよりも戻り値のモックを作成しやすくするためにより多くの労力が費やされます)。基本的に、彼らが簡単にすることを私が考えることができるものは何もありません...

戻り値FTW。

編集:何が起こっているのかに関して...

基本的に、「out」パラメーターの引数を渡すときは、変数を渡す必要あります。(配列要素も変数として分類されます。)呼び出すメソッドには、パラメーター用のスタックに「新しい」変数がありません-変数をストレージに使用します。変数の変更はすぐに表示されます。違いを示す例を次に示します。

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

結果:

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

違いは「ポスト」ステップ、つまりローカル変数またはパラメーターが変更された後です。ReturnValueテストでは、これは静的value変数に影響を与えません。OutParameterテストでは、value変数は次の行によって変更されます。tmp = 10;


2
あなたは私に戻り値がはるかに良いと信じさせました:)。しかし、私はまだ「深く」何が起こるのだろうと思います。つまり、戻り値と出力パラメーターは、作成、割り当て、返される方法が異なりますか?
クアンマイ

1
TryParseは、outパラメータを使用することが適切でクリーンな場合の最良の例です。私は基本的にTryParseと同じパターンである場合(WorkSucceeded(リスト外<文字列>のエラー)のような特別な場合にそれを使用しているが
チャドグラント

2
貧弱で役に立たない答え。この質問のコメントで説明されているように、outパラメータにはいくつかの便利な利点があります。アウトとリターンのどちらを優先するかは、状況によって異なります。
aaronsnoswell 2012

2
@aaronsnoswell:どの正確なコメントですか?の例はvoidメソッドでDictionary.TryGetValueないため、ここで適用できません。戻り値ではなくoutパラメーターが必要な理由を説明できますか?(私にとっても、個人的にはすべての出力情報を含む戻り値を好みます。私がどのように設計したかについては、NodaTimeの例を参照してください。)TryGetValueParseResult<T>
Jon Skeet

2
@ジム:私は同意しないことに同意する必要があると思います。これで頭を殴るのはあなたの言い分だと思いますが、それは彼らが何をしているのかを知っている人にとってあまりフレンドリーではありません。戻り値よりも例外の良い点は、簡単にそれを無視して何も起こらなかったかのように続けることができないことです...一方、戻り値とoutパラメータの両方を使用すると、値に対して何も実行できません。
Jon Skeet、2014

26

何が良いかは、特定の状況によって異なります。理由の1つは、out1つのメソッド呼び出しから複数の値を返すのを容易にするためです。

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

    return input;
}

つまり、どちらかが他の定義よりも優れているわけではありません。ただし、たとえば上記の状況でない限り、通常は単純なリターンを使用します。

編集: これは、キーワードが存在する理由の1つを示すサンプルです。上記は決してベストプラクティスとは見なされません。


2
私はこれに同意しません、なぜあなたは4つのintを持つデータ構造を返さないのですか?これは本当に混乱します。
チャドグラント

1
明らかに、複数の値を返す方法はもっと(そしてより良い)あります。私は、OPがそもそも存在する理由を説明しています。
火葬丘

1
@Cloudに同意します。それが最善の方法ではないからといって、それが存在してはならないという意味ではありません。
セレブラス2009年

まあ、コードは1つにコンパイルすることさえできません...そして、少なくともそれは悪い習慣と見なされている/好ましくないと見なされていることを述べるべきです。
チャドグラント

1
コンパイルされませんか?なんで?このサンプルは、完璧にコンパイルできます。そして、私がベストプラクティスのレッスンではなく、特定の構文/キーワードが存在する理由のサンプルを提供します。
火葬丘

23

通常、outパラメータよりも戻り値を優先する必要があります。2つのことを実行する必要のあるコードを記述している場合、outパラメータは必要な悪です。これの良い例は、Tryパターン(Int32.TryParseなど)です。

2つのメソッドの呼び出し元が何をする必要があるかを考えてみましょう。最初の例では、これを書くことができます...

int foo = GetValue();

変数を宣言して、メソッドを介して1行で割り当てることができることに注意してください。または2番目の例は次のようになります...

int foo;
GetValue(out foo);

変数を前もって宣言し、コードを2行で記述する必要があります。

更新

これらの種類の質問をするときに調べるのに適した場所は、.NET Framework設計ガイドラインです。本のバージョンをお持ちの場合は、Anders Hejlsbergなどによるこの件の注釈(184〜185ページ)を見ることができますが、オンラインバージョンはこちらです...

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

APIから2つのものを返す必要がある場合は、それらをstruct / classでラップする方がoutパラメータよりも優れています。


すばらしい答えです。特に、(一般的ではない)out変数を開発者に使用させる(一般的な)関数であるTryParseへの参照。
セレブラス2009年

12

outまだ言及されていないparam を使用する理由は1つあります。呼び出し側のメソッドはそれを受け取る必要があります。メソッドが生成する値を呼び出し側が破棄してはならない場合、それをout呼び出し側に明示的に受け入れさせる:

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

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

もちろん、呼び出し元はまだparam のを無視できoutますが、あなたはそれに注意を呼びかけました。

これはまれな必要です。多くの場合、真の問題には例外を使用するか、「FYI」の状態情報を含むオブジェクトを返す必要がありますが、これが重要な状況が発生する可能性があります。


8

主に好みです

私は返品を好み、複数の返品がある場合は、結果DTOでそれらをラップできます。

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

5

返される値は1つだけですが、複数の出力パラメーターを持つことができます。

これらの場合にのみ、パラメータを考慮する必要があります。

ただし、メソッドから複数のパラメーターを返す必要がある場合は、オブジェクト指向のアプローチから何を返しているかを調べて、これらのパラメーターを持つオブジェクトまたは構造体を返す方がよいかどうかを検討する必要があります。したがって、再び戻り値に戻ります。


5

ほとんどの場合、戻り値を使用する必要があります。' out'パラメータは、多くのAPI、構成性などに多少の摩擦を生じさせます。

思い浮かぶ最も注目すべき例外は、TryParseパターンなどを使用して複数の値(.Net Frameworkには4.0までタプルがない)を返したい場合です。


良い方法かどうかはわかりませんが、Arraylistを使用して複数の値を返すこともできます。
BA

@BAいいえ、それは良い習慣ではありません。他のユーザーは、このArraylistが何を含んでいるのか、またはどの位置にいるのかを知らないでしょう。たとえば、読み取ったバイトの量と値も返したいと思います。名前が付けられたタプルまたはoutの方がはるかに優れています。ArrayList、またはその他のリストは、人のリストなど、物事のコレクションを返したい場合にのみ役立ちます。
sLw 2018

2

この単純な例では、次のいずれかを使用するのではなく、以下を使用します。

public int Value
{
    get;
    private set;
}

しかし、それらはすべて非常に同じです。通常、メソッドから複数の値を渡す必要がある場合にのみ、 'out'を使用します。メソッドとの間で値を送信する場合は、「ref」を選択します。私の方法は、値を返すだけの場合に最適ですが、パラメーターを渡して値を取得したい場合は、おそらく最初の選択肢を選択します。


2

アンマネージメモリを使用する場合に役立つと思われるいくつかのシナリオの1つだと思います。「返された」値は、自分で破棄するのではなく、手動で破棄する必要があることを明確にしたいと思います。 。


2

さらに、戻り値は非同期設計パラダイムと互換性があります。

refまたはoutパラメータを使用する場合、関数「async」を指定することはできません。

要約すると、戻り値を使用すると、メソッドの連鎖、より明確な構文(呼び出し元が追加の変数を宣言する必要をなくすこと)が可能になり、将来的に大幅な変更を加えることなく非同期設計が可能になります。


「非同期」指定についての重要なポイント。他の誰かがそれについて言及していると思いました。連鎖-式として戻り値(実際には関数自体)を使用することも、別の重要な利点です。それは、プロセスからものを取得する方法だけを検討すること(「バケット」-タプル/クラス/構造体の議論)と、プロセス自体を単一の値に置き換えることができる式のように扱うこと(関数自体、1つの値のみを返すため)。
user1172173 2017年

1

どちらも目的が異なり、コンパイラーによって同じように扱われません。メソッドが値を返す必要がある場合は、returnを使用する必要があります。Outは、メソッドが複数の値を返す必要がある場合に使用されます。

returnを使用すると、データは最初にメソッドスタックに書き込まれ、次に呼び出し元のメソッドに書き込まれます。outの場合は、呼び出し元のメソッドスタックに直接書き込まれます。これ以上の違いがあるかどうかはわかりません。


メソッドはスタックしますか?私はc#の専門家ではありませんが、x86はスレッドごとに1つのスタックしかサポートしません。メソッドの「フレーム」は戻り時に割り振り解除され、コンテキスト切り替えが発生した場合は、割り振り解除されたスタックが上書きされる可能性があります。cでは、すべての戻り値はレジストリeaxに入ります。オブジェクト/構造体を返す場合は、それらをヒープに割り当てる必要があり、ポインタはeaxに置かれます。
StefanLundström

1

他の人が言ったように:パラメータではなく戻り値。

「フレームワークデザインガイドライン」(第2版)という本をお勧めしますか?184〜185ページには、パラメーターを除外する理由が記載されています。本全体が、あらゆる種類の.NETコーディングの問題について正しい方向に導きます。

フレームワーク設計ガイドラインと関連しているのは、静的分析ツールFxCopの使用です。これはMicrosoftのサイトで無料でダウンロードできます。コンパイルしたコードでこれを実行し、それが何を言っているかを確認してください。それが何百と何百もの不平を言うなら...慌てる必要はありません!ありとあらゆるケースについてそれが言っていることを落ち着いて注意深く見てください。急いで物事を直さないでください。それがあなたに伝えていることから学びましょう。あなたは習得への道に置かれます。


1

ブール型の戻り値でoutキーワードを使用すると、コードの膨張を抑制し、読みやすさを向上させることができます。(主に、outパラメータの追加情報がしばしば無視される場合。)たとえば:

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

対:

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

1

本当の違いはありません。メソッドが複数の値を返すことができるように、出力パラメーターはC#にあります。それがすべてです。

ただし、多少の違いはありますが、どれも重要ではありません。

outパラメータを使用すると、次のような2行を使用する必要があります。

int n;
GetValue(n);

戻り値を使用すると、1行で実行できます。

int n = GetValue();

もう1つの違い(値の型に対してのみ、C#が関数をインライン化しない場合にのみ正しい)は、関数が戻るときに戻り値を使用すると必ず値のコピーが作成され、OUTパラメーターを使用すると必ずしもそうではないことです。


0

メソッドで宣言したオブジェクトを返そうとする場合、outはより便利です。

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

戻り値は、メソッドによって返される通常の値です。

どこを通りのパラメータ、うまくとref C#の2つのキー・ワードで彼らは、などの変数を渡すことができます参照

refoutの大きな違いは、refの前とで初期化する必要があることです。


-2

私はこの質問を調べるつもりはないと思いますが、私は非常に経験豊富なプログラマーであり、よりオープンマインドな読者の何人かが注意を払うことを望んでいます。

値を返す手続き(VRP)が確定的で純粋であるため、オブジェクト指向プログラミング言語に適していると思います。

'VRP'は、式の一部として呼び出される関数の最新の学術名であり、式の評価中に概念的に呼び出しを置き換える戻り値を持っています。たとえばx = 1 + f(y)、関数などのステートメント内f VRPとしてています。

「確定的」とは、関数の結果がそのパラメーターの値のみに依存することを意味します。同じパラメーター値で再度呼び出すと、同じ結果が確実に得られます。

「純粋」は副作用がないことを意味します。関数を呼び出しても、結果の計算以外は何も行われません。これは重要ではないことを意味すると解釈できます、実際に副作用がため、たとえばVRPが呼び出されるたびにデバッグメッセージを出力する場合は、おそらく無視できます。

したがって、C#で関数が確定的ではない場合、void関数を(つまり、VRPではなく)関数にする必要があると私は言います。outまた、返す必要がある値は、またはrefパラメーターで返す必要があります。

たとえば、データベーステーブルから一部の行を削除する関数があり、削除した行の数を返すようにする場合は、次のように宣言する必要があります。

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

この関数を呼び出したいが、取得しない場合 count場合は、常にオーバーロードを宣言できます。

このスタイルがオブジェクト指向プログラミングに適している理由を知りたい場合があります。大まかに言えば、これは(少し不正確に)「手続き型プログラミング」と呼ばれるプログラミングスタイルに適合し、オブジェクト指向プログラミングにより適した手続き型プログラミングスタイルです。

どうして?オブジェクトの古典的なモデルは、それらにプロパティ(別名属性)があり、それらのプロパティを読み取って更新することにより、(主に)オブジェクトに問い合わせて操作することです。プロパティの取得と設定を行う操作の間に任意のコードを実行できるため、手続き型プログラミングスタイルを使用すると、これが簡単になります。

手続き型プログラミングの欠点は、あらゆる場所で任意のコードを実行できるため、グローバル変数と副作用を介して、非常に鈍く、バグの影響を受けやすい相互作用が得られることです。

したがって、非常に簡単に言えば、関数を値以外の値に戻すことにより、関数が副作用を引き起こす可能性があることをコードを読んでいる誰かに通知することをお勧めします。


>この関数を呼び出したいがカウントを取得したくない場合は、常にオーバーロードを宣言できます。C#バージョン7(私は思う)以降では、_破棄記号を使用して、outパラメーターを無視できます。例:DeleteBasketItems(category、out _);
討論者
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.