in vs ref vs outを使用する場合


383

先日、誰かがのout代わりにパラメータキーワードを使用する必要があると尋ねられましたref。私は(と思う)の違いを理解している間refおよびout(されたキーワードの前に尋ねた)と最良の説明があることのようですref== inout、私はいつも使用する必要がありますいくつかの(仮想またはコード)の例何outとしませんref

以来ref、より一般的で、なぜあなたは今までに使用しますかout?それは単に構文上の砂糖ですか?


18
usingで渡された変数は、out割り当てられる前から読み取ることはできません。refこの制限はありません。だからそれがあります。
Corey Ogburn

17
つまり、refは入力/出力用であり、はout出力のみのパラメータです。
Tim S.

3
正確には何がわかりませんか?
tnw

4
out関数内で割り当てられる変数もあります。
Corey Ogburn

コーリーに感謝します。しかし、私はすでにそうではありません。私のポイントは、これの利点は何ですか。実際、私は、refパラメーターを使用して、outパラメーターを使用することでは実現できない機能を実現できるシナリオを示す例が必要です。逆も同様です。
Rajbir Singh

回答:


399

out必要な場合以外は使用してくださいref

これは、データをマーシャリングする必要がある場合などに大きな違いをもたらします。したがって、メソッドが初期値を使用しないときに初期値をマーシャリングしないようにする必要があります。

それを超えて、それはまた、宣言または読者の読者に、初期値が関連する(そして、保持される可能性がある)か、破棄されるかを示します。

小さな違いとして、出力パラメーターを初期化する必要はありません。

の例out

string a, b;
person.GetBothNames(out a, out b);

GetBothNamesは2つの値をアトミックに取得するメソッドですが、メソッドはaとbが何であっても動作を変更しません。呼び出しがハワイのサーバーに送られる場合、初期値をここからハワイにコピーすることは帯域幅の浪費です。refを使用した同様のスニペット:

string a = String.Empty, b = String.Empty;
person.GetBothNames(ref a, ref b);

これは、aとbの初期値が関連しているように見えるため、読者を混乱させる可能性があります(メソッド名は関連していないことを示しています)。

の例ref

string name = textbox.Text;
bool didModify = validator.SuggestValidName(ref name);

ここで、初期値はメソッドに関連しています。


5
「それは実際にはそうではない。」-どういう意味ですか?
peterchen 2009年

3
refデフォルト値に使用したくない。
C.Evenhuis

155
後世のために:ここで述べられているように、他の誰も言及していないように見えるもう一つの違い。以下のためoutのパラメータ、呼び出し方法が必要メソッドが戻る前に値を割り当てます。-あなたはしていない持っている REFパラメータを指定して何かをします。
brichins '20

3
@brichins リンク先の「コメント(コミュニティの追加)」セクションを参照してください。これはVS 2008のドキュメントで修正されたエラーです。
Bharat Ram V

13
@brichins 呼び出されたメソッドではなく、値を割り当てる必要があります。zverev.eugeneこれは、VS 2008のドキュメントで修正されたものです。
Segfault 2014年

72

パラメータが使用されておらず、設定されていることを示すには、outを使用します。これにより、呼び出し元は常にパラメーターを初期化していることを理解できます。

また、refとoutは単なる値型ではありません。また、メソッド内から参照型が参照しているオブジェクトをリセットすることもできます。


3
+1私はそれが参照型にも使用できることを知りませんでした、素晴らしい明確な回答、ありがとう
Dale

@brichins:いいえ、できません。 outパラメーターは、関数への入り口で未割り当てとして扱われます。最初にいくつかの値を明確に割り当てるまで、それらの値を検査することはできません-関数が呼び出されたときにパラメーターが持っていた値を使用する方法はまったくありません。
Ben Voigt

確かに、内部割り当ての前に値にアクセスすることはできません。パラメータ自体後でメソッドで使用できるということを言及していました-ロックされていません。これを実際に行うべきかどうかは、(設計に関して)異なる議論です。私はそれが可能だったことを指摘したかっただけです。説明をありがとう。
brichins 2013

2
@ดาว:参照型パラメーターで渡す場合、渡すのはオブジェクト自体ではなく参照の値であるため、参照型で使用できます。したがって、それはまだ値渡しです。
Tarik

38

つまり、意味的にrefは、「in」と「out」の両方の機能をout提供し、「out」機能のみを提供するということです。考慮すべき点がいくつかあります。

  1. outパラメータを受け入れるメソッドは、返す前のある時点で、変数に値を割り当てる必要があります。このパターンは、などのキー/値データストレージクラスの一部でDictionary<K,V>見られますTryGetValue。この関数は、out取得した場合の値を保持するパラメーターを取ります。呼び出し側が値を渡すことは意味がありませんこの機能、そうoutした場合には(一部の値は、それが「本物」のデータでない場合でも、呼び出しの後に変数になることを保証するために使用されますTryGetValueキーは存在しません)。
  2. outref相互運用コードを処理するときに、パラメーターが異なる方法でマーシャリングされます

また、余談ですが、参照型と値型は値の性質が異なりますが、アプリケーションのすべての変数は、参照型であっても、値を保持するメモリの場所を指すことに注意してください。参照型では、そのメモリの場所に含まれる値が別のメモリの場所。関数に値を渡す(または他の変数割り当てを行う)と、その変数の値が他の変数にコピーされます。値タイプの場合、それはタイプのコンテンツ全体がコピーされることを意味します。参照タイプの場合、それはメモリ位置がコピーされることを意味します。どちらにしても、変数に含まれるデータのコピーが作成されます。これが保持する唯一の真の関連性は、割り当てのセマンティクスを扱います。変数を割り当てる場合、または値で渡す場合(デフォルト)、元の(または新しい)変数に新しい割り当てを行っても、他の変数には影響しません。参照タイプの場合、はい、変更はインスタンスに両方で使用できますが、これは実際の変数が別のメモリ位置へのポインタにすぎないためです。変数の内容(メモリの場所)は実際には変更されませんでした。

refキーワードを渡すと、元の変数関数パラメーターの両方が実際には同じメモリ位置を指すことになります。これも、割り当てセマンティクスのみに影響します。変数の1つに新しい値が割り当てられると、他の変数が同じメモリ位置を指しているため、新しい値が反対側に反映されます。


1
呼び出されたメソッドが出力パラメーターに値を割り当てるという要件は、基礎となるILではなく、c#コンパイラーによって適用されることに注意してください。したがって、VB.NETで作成されたライブラリは、その規則に準拠していない場合があります。
jmoreno 2014年

refのような音は、実際にはC ++(*)の逆参照記号に相当します。C#のPassby参照は、C / C ++がダブルポインター(ポインターへのポインター)として参照するものと同等でなければならないため、refは最初のポインターを逆参照し、呼び出されたメソッドがコンテキスト内の実際のオブジェクトのメモリ位置にアクセスできるようにする必要があります。
ComeIn 2014年

私は実際には正しいキーTryGetValueを使用することをお勧めしますが、キーが見つからない場合は明示的に使用refしませんout
NetMage 2017年

27

これはコンパイルコンテキストに依存します(以下の例を参照)。

outそして、ref表す参照渡し変数、まだ両方のrefマーシャリング(相互運用:UmanagedToManagedTransitionまたはその逆)の文脈において重要な違いであることができる、渡される前に初期化される変数を必要とします

MSDNは警告します:

参照渡しの概念と参照タイプの概念を混同しないでください。2つの概念は同じではありません。メソッドパラメータは、値タイプか参照タイプかに関係なく、refによって変更できます。参照によって渡される場合、値タイプのボックス化はありません。

公式のMSDNドキュメントから:

outキーワードを指定すると、引数が参照渡しされます。これは、refが変数を渡す前に初期化する必要があることを除いて、refキーワードと同様です。

refキーワードを使用すると、引数は値ではなく参照で渡されます。参照渡しの効果は、メソッドのパラメーターへの変更が、呼び出し側メソッドの基になる引数変数に反映されることです。参照パラメーターの値は、基になる引数変数の値と常に同じです。

引数が割り当てられたときに、outとrefが実際に同じであることを確認できます。

CILの例

次の例を考えてみましょう

static class outRefTest{
    public static int myfunc(int x){x=0; return x; }
    public static void myfuncOut(out int x){x=0;}
    public static void myfuncRef(ref int x){x=0;}
    public static void myfuncRefEmpty(ref int x){}
    // Define other methods and classes here
}

CILで、の指示myfuncOutとはmyfuncRef予想通り同じです。

outRefTest.myfunc:
IL_0000:  nop         
IL_0001:  ldc.i4.0    
IL_0002:  starg.s     00 
IL_0004:  ldarg.0     
IL_0005:  stloc.0     
IL_0006:  br.s        IL_0008
IL_0008:  ldloc.0     
IL_0009:  ret         

outRefTest.myfuncOut:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldc.i4.0    
IL_0003:  stind.i4    
IL_0004:  ret         

outRefTest.myfuncRef:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldc.i4.0    
IL_0003:  stind.i4    
IL_0004:  ret         

outRefTest.myfuncRefEmpty:
IL_0000:  nop         
IL_0001:  ret         

nop:操作なし、ldloc:ローカルにロード、stloc:ローカルにスタック、ldarg:引数をロード、bs.s:ターゲットへのブランチ...

(参照:CIL指示のリスト


23

以下は、C#Out Vs Refのこのcodeproject記事から私が引き出したいくつかのメモです

  1. 関数またはメソッドからの複数の出力が予想される場合にのみ使用してください。構造についての考えは、同じことのための良い選択肢にもなり得ます。
  2. REFとOUTは、データが呼び出し元から呼び出し先に、またはその逆にどのように渡されるかを指示するキーワードです。
  3. REFでは、データは双方向に渡されます。発信者から着信者へ、およびその逆。
  4. In Outデータは、呼び出し先から呼び出し元への一方向のみを渡します。この場合、発信者が着信者にデータを送信しようとすると、見落とされたり拒否されたりします。

あなたが視覚的な人である場合は、実際にその違いを示すこのyourtubeビデオをご覧くださいhttps://www.youtube.com/watch?v=lYdcY5zulXA

下の画像は違いをより視覚的に示しています

C#出力と参照


1
one-waytwo-wayここでは用語が誤用されている可能性があります。実際には両方とも双方向ですが、概念的な動作はパラメーターの参照と値によって異なります
ibubi

17

使用する必要があります refパラメータの読み取りと書き込みを計画し場合ます。out書くつもりなら、使う必要があります。実際にoutは、複数の戻り値が必要な場合、または通常の出力メカニズムを出力に使用したくない場合に使用します(ただし、これはまれなはずです)。

これらのユースケースを支援する言語力学があります。 Refパラメータは、メソッドに渡される前に初期化されている必要があり(読み書き可能であることを強調)、out値が割り当てられる前に読み取ることはできず、最後に書き込まれることが保証されますメソッド(書き込み専用であることを強調)。これらの原則に違反すると、コンパイル時エラーが発生します。

int x;
Foo(ref x); // error: x is uninitialized

void Bar(out int x) {}  // error: x was not written to

たとえば、int.TryParsea boolを返し、out intパラメーターます。

int value;
if (int.TryParse(numericString, out value))
{
    /* numericString was parsed into value, now do stuff */
}
else
{
    /* numericString couldn't be parsed */
}

これは、数値結果と変換が成功したかどうかの2つの値を出力する必要がある状況の明確な例です。CLRの作成者は、outint以前の状態を気にしないここすることにしました。

についてはref、以下をご覧くださいInterlocked.Increment

int x = 4;
Interlocked.Increment(ref x);

Interlocked.Incrementの値をアトミックに増分しますx。あなたが読む必要があるのでxをインクリメント、これrefはより適切な状況です。xに渡される前の状態を完全に気にしIncrementます。

C#の次のバージョンでは、outパラメーターで変数を宣言することも可能になり、出力のみの性質がさらに強調されます。

if (int.TryParse(numericString, out int value))
{
    // 'value' exists and was declared in the `if` statement
}
else
{
    // conversion didn't work, 'value' doesn't exist here
}

あなたの応答をzneakに感謝します。しかし、なぜパラメーターの読み書きに使用できなかったのか説明してくれませんか?
Rajbir Singh

@RajbirSingh、outパラメーターが必ずしも初期化されていないため、out何かを書き込むまで、コンパイラーはパラメーターからの読み取りを許可しません。
zneak

zneak、私はあなたに同意しました。ただし、以下の例では、出力パラメーターを読み取りおよび書き込みとして使用できます。string name = "myName"; private void OutMethod(out string nameOut){if(nameOut == "myName"){nameOut = "Rajbir Singh in out method"; }}
Rajbir Singh

1
@RajbirSingh、あなたの例はコンパイルされません。まだ何も割り当てられていないためnameOutifステートメントを読み取ることができません。
zneak

@zneakに感謝します。あなたは、絶対に正しい。コンパイルされません。私の助けに感謝します、そして今それは私にとって理にかなっています:)
Rajbir Singh

7

outは、のより制限されたバージョンですref

メソッド本体ではout、メソッドを終了する前にすべてのパラメーターに割り当てる必要があります。また、outパラメータに割り当てられた値は無視されますが、ref割り当てられる必要があります。

したがってout、次のことが可能になります。

int a, b, c = foo(out a, out b);

ここでref、aとbを割り当てる必要があります。


どちらかといえばout、より制約の少ないバージョンです。 ref「前提条件:変数は確実に割り当てられ、事後条件:変数は確実に割り当てられます」、out「事後条件:変数は確実に割り当てられます」のみです(そして、予想どおり、前提条件が少ない関数の実装には、より多くが必要です)
Ben Voigt

@BenVoigt:それはあなたが見ている方向によって異なりますね:)私はコーディングの柔軟性の観点から制約を意味したと思います(?)
leppie 2013

7

それが聞こえる方法:

アウト =のみ初期化/パラメータ(パラメータが空でなければならない)それを返す埋めるアウトプレーン

REF =参照、(多分値)の標準的なパラメータを、しかし機能は、それをmodifiyすることができます。


outパラメータ変数は、メソッドに渡す前に値を取得できます。
BenceVégert18年

6

あなたは使うことができます out、パラメータ調整剤としてまたはインターフェイスおよび代表者でジェネリック型パラメータ宣言では、2つのコンテキスト(各詳細情報へのリンクです)でコンテキストキーワードを。このトピックでは、パラメーター修飾子について説明しますが、ジェネリック型パラメーター宣言については、この他のトピックを参照してください。

outキーワードは、引数が参照渡しされます。これはrefキーワードに似refていますが、渡される前に変数を初期化する必要がある点が異なります。outパラメータを使用するには、メソッド定義と呼び出しメソッドの両方でoutキーワードを明示的に使用する必要があります。例:C#

class OutExample
{
    static void Method(out int i)
    {
        i = 44;
    }
    static void Main()
    {
        int value;
        Method(out value);
        // value is now 44
    }
}

変数は out引数前に初期化する必要はありませんが、呼び出されたメソッドは、メソッドが戻る前に値を割り当てる必要があります。

けれどもrefおよびoutキーワードは異なる実行時の動作を引き起こし、それらはコンパイル時にメソッドシグネチャの一部とはみなされません。したがって、一方のメソッドがref引数を取り、もう一方のメソッドが引数を受け取るという唯一の違いがある場合、メソッドをオーバーロードすることはできませんout。たとえば、次のコードはコンパイルされません:C#

class CS0663_Example
{
    // Compiler error CS0663: "Cannot define overloaded 
    // methods that differ only on ref and out".
    public void SampleMethod(out int i) { }
    public void SampleMethod(ref int i) { }
}

ただし、次のように、一方のメソッドがrefまたはout引数を取り、もう一方がどちらも使用しない場合は、オーバーロードを実行できます。C#

class OutOverloadExample
{
    public void SampleMethod(int i) { }
    public void SampleMethod(out int i) { i = 5; }
}

プロパティは変数ではないため、outパラメーターとして渡すことはできません。

配列の受け渡しについてはref、およびを使用した配列の受け渡しを参照してください。out(C#プログラミングガイド)。

refおよびoutキーワードは、次の種類のメソッドには使用できません。

Async methods, which you define by using the async modifier.

Iterator methods, which include a yield return or yield break statement.

outメソッドが複数の値を返すようにする場合は、メソッドの宣言が役立ちます。次の例ではout、1つのメソッド呼び出しで3つの変数を返すために使用しています。3番目の引数はnullに割り当てられることに注意してください。これにより、メソッドはオプションで値を返すことができます。C#

class OutReturnExample
{
    static void Method(out int i, out string s1, out string s2)
    {
        i = 44;
        s1 = "I've been returned";
        s2 = null;
    }
    static void Main()
    {
        int value;
        string str1, str2;
        Method(out value, out str1, out str2);
        // value is now 44
        // str1 is now "I've been returned"
        // str2 is (still) null;
    }
}

6

どのように使用するinか、outまたはrefC#で?

  • のすべてのキーワードC#は同じ機能を持っていますが、いくつかの境界があります。
  • in 引数は呼び出されたメソッドで変更できません。
  • ref 引数は変更される場合があります。
  • ref 呼び出し元が使用する前に初期化する必要があります。メソッドで読み取りおよび更新できます。
  • out 引数は呼び出し側が変更する必要があります。
  • out 引数はメソッドで初期化する必要があります
  • in引数として渡される変数は、メソッド呼び出しで渡される前に初期化する必要があります。ただし、呼び出されたメソッドは値を割り当てたり、引数を変更したりすることはできません。

あなたは使用することはできませんinrefoutメソッドの次の種類のキーワードを:

  • async修飾子を使用して定義する非同期メソッド
  • イテレータ方法などが、yield returnまたはyield breakステートメントを。

5

OPのコメントを明確にするために、refとoutでの使用は、「メソッド外で宣言された値型または構造体への参照」であり、すでに正しく設定されていません。

参照型であるStringBuilderでのrefの使用を検討してください。

private void Nullify(StringBuilder sb, string message)
{
    sb.Append(message);
    sb = null;
}

// -- snip --

StringBuilder sb = new StringBuilder();
string message = "Hi Guy";
Nullify(sb, message);
System.Console.WriteLine(sb.ToString());

// Output
// Hi Guy

これに付け加えて:

private void Nullify(ref StringBuilder sb, string message)
{
    sb.Append(message);
    sb = null;
}

// -- snip --

StringBuilder sb = new StringBuilder();
string message = "Hi Guy";
Nullify(ref sb, message);
System.Console.WriteLine(sb.ToString());

// Output
// NullReferenceException

4

refとして渡される引数は、メソッドに渡す前に初期化する必要がありますが、outパラメーターは、メソッドに渡す前に初期化する必要はありません。


4

なぜ使いたくないのですか?

呼び出されたメソッドから戻ったときに変数が初期化されることを他の人に知らせるために!

上記のように:「outパラメータの場合、メソッドを返す前、呼び出し側のメソッドで値を割り当てる必要があります。」

例:

Car car;
SetUpCar(out car);
car.drive();  // You know car is initialized.

4

基本的に両方refoutメソッド間でオブジェクト/値を渡すため

outキーワードを指定すると、引数が参照渡しされます。これはrefキーワードに似ていますが、refでは、変数を渡す前に変数を初期化する必要がある点が異なります。

out :引数は初期化されていません。メソッドで初期化する必要があります

ref :引数は既に初期化されており、メソッドで読み取って更新できます。

参照タイプの「ref」の用途は何ですか?

与えられた参照を別のインスタンスに変更できます。

知ってますか?

  1. refおよびoutキーワードは異なるランタイム動作を引き起こしますが、それらはコンパイル時にメソッドシグネチャの一部とは見なされません。したがって、唯一の違いが1つのメソッドがref引数を取り、もう1つのメソッドがout引数を取ることである場合、メソッドはオーバーロードできません。

  2. 次の種類のメソッドには、refキーワードとoutキーワードを使用できません。

    • async修飾子を使用して定義する非同期メソッド。
    • 反復メソッドには、yield returnまたはyield breakステートメントが含まれます。
  3. プロパティは変数ではないため、outパラメータとして渡すことはできません。


4

C#7に関する追加のメモ:
C#7では、outを使用して変数を事前宣言する必要はありません。したがって、次のようなコード:

public void PrintCoordinates(Point p)
{
  int x, y; // have to "predeclare"
  p.GetCoordinates(out x, out y);
  WriteLine($"({x}, {y})");
}

このように書くことができます:

public void PrintCoordinates(Point p)
{
  p.GetCoordinates(out int x, out int y);
  WriteLine($"({x}, {y})");
}

出典: C#7の新機能


4

それでも良い要約の必要性を感じます。これが私が思いついたものです。

概要、

関数内にいるとき、これは可変データアクセスコントロールを指定する方法です

in = R

out = Rの前にWでなければならない

ref = R + W


説明、

in

関数はその変数のみを読み取ることができます。

out


関数READの前に変数に書き込みを行う必要があるため、変数を最初に初期化しないでください。

ref

関数はその変数に対して読み取り/書き込みを行うことができます。


なぜそのように名付けられているのですか?

データが変更される場所に焦点を当て、

in

関数に入る前にのみデータを設定する必要があります。

out

データは、機能を終了する前にのみ設定する必要があります。

ref

関数を入力する前にデータを設定する必要があります。
機能を終了する前にデータを設定できます。


おそらく(in / out / ref)は(r / wr / rw)に名前を変更する必要があります。かもしれないし、そうでないかもしれませんが、in / outはより良い比喩です。
ティンカー

0

ことに留意すべきであるinの有効なキーワードである7.2版のC#

inパラメーター修飾子は、C#7.2以降で使用できます。以前のバージョンではコンパイラエラーCS8107が生成されます(「機能「読み取り専用参照」はC#7.0では使用できません。言語バージョン7.2以降を使用してください。))コンパイラ言語バージョンを構成するには、C#言語バージョンの選択を参照してください。

...

inキーワードを使用すると、引数が参照渡しされます。これは、仮パラメーターを引数の別名にします。これは変数でなければなりません。つまり、パラメータに対する操作はすべて引数に対して行われます。これはrefまたはoutキーワードに似ていますが、呼び出されたメソッドではin引数を変更できない点が異なります。ref引数は変更可能ですが、out引数は呼び出されたメソッドによって変更する必要があり、これらの変更は呼び出し側のコンテキストで確認できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.