web-dev-qa-db-ja.com

'ref'キーワードと 'out'キーワードの違いは何ですか?

関数でオブジェクトを変更できるように、オブジェクトを渡す必要がある場所に関数を作成しています。違いは何ですか:

public void myFunction(ref MyClass someClass)

そして

public void myFunction(out MyClass someClass)

どちらを使用すればよいですか、またその理由は何ですか。

816
TK.

refは、関数に入る前にオブジェクトが初期化されていることをコンパイラに伝えます。一方、outは、オブジェクトが関数内で初期化されることをコンパイラに伝えます。

refは双方向ですが、outはout-onlyです。

1077
Rune Grimstad

ref修飾子は以下のことを意味します。

  1. 値はすでに設定されています
  2. メソッドはそれを読んで修正することができます。

out修飾子は以下のことを意味します。

  1. 値が設定されていないため、設定されているuntilメソッドで読み取ることができません。
  2. メソッドmustは戻る前に設定します。
483
Anton Kolesov

DomがTPSレポートに関するメモについてPeterのブースに登場したとしましょう。

もしDomがrefの議論であれば、彼はそのメモのコピーを印刷していたでしょう。

もしDomが議論の余地がなかったら、彼は彼が彼と一緒に持っていくために彼にそのメモの新しいコピーを印刷させるでしょう。

137

私は説明を試みるつもりです:

値型がどのように正しく機能するのか理解していると思いますか。値の型は(int、long、structなど)です。 refコマンドなしでそれらを関数に送ると、 data がコピーされます。関数内でそのデータにあなたがしたことは、オリジナルにではなくコピーにのみ影響します。 refコマンドはACTUALデータを送信し、変更があると関数外のデータに影響します。

わかりにくい部分は、参照型です。

参照型を作成しましょう。

List<string> someobject = new List<string>()

someobject を新しくすると、2つの部分が作成されます。

  1. someobject のデータを保持するメモリブロック。
  2. そのデータブロックへの参照(ポインタ)。

参照せずに someobject をメソッドに送信すると、データではなく reference ポインタがコピーされます。だから今あなたはこれを持っている:

(outside method) reference1 => someobject
(inside method)  reference2 => someobject

2つの参照が同じオブジェクトを指しています。 reference2を使用して someobject のプロパティを変更すると、reference1が指すのと同じデータに影響します。

 (inside method)  reference2.Add("SomeString");
 (outside method) reference1[0] == "SomeString"   //this is true

Reference2を無効にしたり、新しいデータを指し示したりしても、reference1には影響せず、データreference1も指していません。

(inside method) reference2 = new List<string>();
(outside method) reference1 != null; reference1[0] == "SomeString" //this is true

The references are now pointing like this:
reference2 => new List<string>()
reference1 => someobject

さて、メソッドにrefを使って someobject を送るとどうなりますか? 実際の参照 / someobject がメソッドに送信されます。これで、データへの参照は1つだけになりました。

(outside method) reference1 => someobject;
(inside method)  reference1 => someobject;

しかし、これはどういう意味ですか? 2つのことを除いて、refによってではなくsomeobjectを送信するのとまったく同じように動作します。

1)メソッド内の参照を無効にすると、メソッド外の参照も無効になります。

 (inside method)  reference1 = null;
 (outside method) reference1 == null;  //true

2)これで、参照を完全に異なるデータ位置に向けることができ、関数外の参照は新しいデータ位置を指すようになります。

 (inside method)  reference1 = new List<string>();
 (outside method) reference1.Count == 0; //this is true
51
James Roland

refはout にあります。

あなたの要求を満たすのに十分なところならどこでもoutを優先して使うべきです。

27
Ruben Bartelink

でる:

C#では、メソッドは1つの値しか返すことができません。複数の値を返したい場合は、outキーワードを使用できます。 out修飾子は、参照による戻り値として返されます。最も単純な答えは、メソッドから値を取得するためにキーワード「out」が使用されることです。

  1. 呼び出し元の関数で値を初期化する必要はありません。
  2. 呼び出された関数に値を代入しなければ、コンパイラはエラーを報告します。

参照:

C#では、int、float、doubleなどの値型をメソッドパラメータの引数として渡すと、値によって渡されます。したがって、パラメータ値を変更しても、メソッド呼び出しの引数には影響しません。しかし、もしあなたが“ ref”キーワードでパラメータをマークすると、それは実際の変数に反映されます。

  1. 関数を呼び出す前に変数を初期化する必要があります。
  2. メソッドのrefパラメータに値を代入することは必須ではありません。値を変更しない場合は、「参照」としてマークする必要がありますか。
16
Nazmul Hasan

犬、猫の例を拡張する。 refを使った2番目のメソッドは、呼び出し元によって参照されているオブジェクトを変更します。それゆえ「猫」!

    public static void Foo()
    {
        MyClass myObject = new MyClass();
        myObject.Name = "Dog";
        Bar(myObject);
        Console.WriteLine(myObject.Name); // Writes "Dog". 
        Bar(ref myObject);
        Console.WriteLine(myObject.Name); // Writes "Cat". 
    }

    public static void Bar(MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

    public static void Bar(ref MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }
12
BBB

参照型(クラス)を渡すので、デフォルトでは実際のオブジェクトへの reference のみが渡されるので、refを使う必要はありません。したがって、常に参照の後ろのオブジェクトを変更します。

例:

public void Foo()
{
    MyClass myObject = new MyClass();
    myObject.Name = "Dog";
    Bar(myObject);
    Console.WriteLine(myObject.Name); // Writes "Cat".
}

public void Bar(MyClass someObject)
{
    someObject.Name = "Cat";
}

クラス内で渡す限り、メソッド内のオブジェクトを変更したい場合はrefを使用する必要はありません。

8
Albic

refoutは、以下の違いを除いて同様に動作します。

  • ref変数は使用前に初期化する必要があります。代入なしでout変数を使用できる
  • outパラメータは、それを使用する関数によって未割り当ての値として扱われる必要があります。そのため、呼び出し元のコードで初期化されたoutパラメータを使用できますが、関数が実行されると値は失われます。
8
gmail user

例によって学ぶ人たちのために(私のように)これは Anthony Kolesovが言っていることです

この点を説明するために、ref、outなどの最小限の例をいくつか作成しました。ベストプラクティスは扱っていません。違いを理解するための例にすぎません。

https://Gist.github.com/2upmedia/6d98a57b68d849ee7091

7
2upmedia

「パン屋」

それは、最初のものがあなたの文字列参照を "Baker"を指すように変更するからです。 refキーワード(=>文字列への参照への参照)で渡したため、参照を変更することは可能です。 2番目の呼び出しは文字列への参照のコピーを取得します。

文字列は最初はなんらかの特別なものに見えます。しかし、stringは単なる参照クラスであり、あなたが定義すれば

string s = "Able";

それからsは "Able"というテキストを含む文字列クラスへの参照です。同じ変数への別の代入

s = "Baker";

元の文字列を変更するのではなく、単に新しいインスタンスを作成してそのインスタンスをポイントさせます。

次の小さなコード例でそれを試すことができます。

string s = "Able";
string s2 = s;
s = "Baker";
Console.WriteLine(s2);

何を期待しますか? s2が元のインスタンスを指している間にs内の参照を別のインスタンスに設定するだけなので、取得できるものはまだ「有効」です。

EDIT:stringも不変です。つまり、既存の文字列インスタンスを変更するようなメソッドやプロパティが存在しないことを意味します(ドキュメント内でインスタンスを見つけようとすることはできますが、:-)は見つかりません)。すべての文字列操作メソッドは新しい文字列インスタンスを返します。 (そのため、StringBuilderクラスを使用するとパフォーマンスが向上することがよくあります)

6
mmmmmmmm

ref は、refパラメータの値がすでに設定されていることを意味します。このメソッドは、値を読み取って変更できます。 refキーワードを使用することは、呼び出し側がパラメータの値を初期化する責任があるということと同じです。


out は、objectの初期化が関数の責任であることをコンパイラに伝えます。関数はoutパラメータに割り当てる必要があります。未割り当てのままにすることはできません。

5
Farhan S.

Out: / returnステートメントは、関数から値を1つだけ返すために使用できます。ただし、出力パラメータを使用して、関数から2つの値を返すことができます。出力パラメータは参照パラメータと似ていますが、メソッドにではなくメソッドからデータを転送する点が異なります。

次の例でこれを説明します。

using System;

namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void getValue(out int x )
      {
         int temp = 5;
         x = temp;
      }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;

         Console.WriteLine("Before method call, value of a : {0}", a);

         /* calling a function to get the value */
         n.getValue(out a);

         Console.WriteLine("After method call, value of a : {0}", a);
         Console.ReadLine();

      }
   }
}

ref: referenceパラメータは、変数のメモリ位置への参照です。パラメータを参照渡しで渡すと、値パラメータとは異なり、これらのパラメータに新しい格納場所は作成されません。参照パラメータは、メソッドに提供される実際のパラメータと同じメモリ位置を表します。

C#では、refキーワードを使用して参照パラメータを宣言します。次の例はこれを示しています。

using System;
namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void swap(ref int x, ref int y)
      {
         int temp;

         temp = x; /* save the value of x */
         x = y;   /* put y into x */
         y = temp; /* put temp into y */
       }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;
         int b = 200;

         Console.WriteLine("Before swap, value of a : {0}", a);
         Console.WriteLine("Before swap, value of b : {0}", b);

         /* calling a function to swap the values */
         n.swap(ref a, ref b);

         Console.WriteLine("After swap, value of a : {0}", a);
         Console.WriteLine("After swap, value of b : {0}", b);

         Console.ReadLine();

      }
   }
}
4
Faisal Naseer

refとoutは、C++のように参照渡しとポインタ渡しのように動作します。

Refの場合は、引数を宣言して初期化する必要があります。

Outの場合、引数は宣言されている必要がありますが、初期化されていてもいなくてもかまいません

        double nbr = 6; // if not initialized we get error
        double dd = doit.square(ref nbr);

        double Half_nbr ; // fine as passed by out, but inside the calling  method you initialize it
        doit.math_routines(nbr, out Half_nbr);
4
RotatingWheel

オーサリング時間:

(1)呼び出しメソッドMain()を作成します

(2)Listオブジェクト(参照型オブジェクト)を作成し、それを変数myListに格納します。

public sealed class Program 
{
    public static Main() 
    {
        List<int> myList = new List<int>();

実行時:

(3)ランタイムは、アドレスを格納するのに十分な幅の#00でスタック上にメモリを割り当てます(#00 = myList、変数名は実際にはメモリ位置のエイリアスにすぎないため)。

(4)ランタイムは、メモリ位置#FFのヒープ上にリストオブジェクトを作成します(これらすべてのアドレスは、たとえばスークです)。

(5)ランタイムはオブジェクトの開始アドレス#FFを#00に格納します(つまり、Listオブジェクトの参照をポインタmyListに格納します)。

オーサリング時間に戻る:

(6)次にListオブジェクトを引数myParamListとして呼び出されたメソッドmodifyMyListに渡し、それに新しいListオブジェクトを割り当てます

List<int> myList = new List<int>();

List<int> newList = ModifyMyList(myList)

public List<int> ModifyMyList(List<int> myParamList){
     myParamList = new List<int>();
     return myParamList;
}

実行時:

(7)ランタイムは呼び出されたメソッドの呼び出しルーチンを起動し、その一部としてパラメータの型をチェックします。

(8)参照型が見つかると、#04でスタック上にパラメータ変数myParamListをエイリアスするためのメモリを確保します。

(9)次に#FFという値も格納します。

(10)ランタイムはヒープ上のメモリ位置#004のリストオブジェクトを作成し、#04の#FFをこの値で置き換えます(または元のListオブジェクトを間接参照し、このメソッドで新しいListオブジェクトを指し示します)。

#00のアドレスは変更されず、#FFへの参照を保持します(または元のmyListポインタは乱されません)。


ref キーワードは、(8)および(9)のランタイムコードの生成をスキップするためのコンパイラ指令です。これは、メソッドパラメータのヒープ割り当てがないことを意味します。 #FFでオブジェクトを操作するために、元の#00ポインタを使用します。元のポインタが初期化されていない場合は、変数が初期化されていないため、ランタイムはそれを進めることができないと文句を言って停止します。

out キーワードはコンパイラ指令で、(9)と(10)を少し変更したものとほぼ同じです。コンパイラーは引数が初期化されていないと想定し、ヒープ上にオブジェクトを作成し、その開始アドレスを引数変数に格納するために(8)、(4)、(5)を続けます。初期化されていないエラーはスローされず、以前に保存された参照はすべて失われます。

3
supi

Ref:refキーワードは引数を参照として渡すために使用されます。これは、そのパラメータの値がメソッド内で変更されると、それが呼び出し元のメソッドに反映されることを意味します。 refキーワードを使用して渡される引数は、呼び出されるメソッドに渡される前に呼び出し側のメソッドで初期化する必要があります。

Out:outキーワードは、refキーワードのように引数を渡すためにも使用されますが、引数に値を割り当てずに引数を渡すことができます。 outキーワードを使用して渡される引数は、呼び出し元のメソッドに戻る前に、呼び出されるメソッドで初期化する必要があります。

public class Example
{
 public static void Main() 
 {
 int val1 = 0; //must be initialized 
 int val2; //optional

 Example1(ref val1);
 Console.WriteLine(val1); 

 Example2(out val2);
 Console.WriteLine(val2); 
 }

 static void Example1(ref int value) 
 {
 value = 1;
 }
 static void Example2(out int value) 
 {
 value = 2; 
 }
}

/* Output     1     2     

メソッドオーバーロードの参照と出力

Refとoutの両方を同時にメソッドのオーバーロードに使用することはできません。ただし、refとoutは実行時には異なる方法で処理されますが、コンパイル時には同じように処理されます(refとoutに対してILを作成している間はCLRは両者を区別しません)。

1
Dejan Ciev
 public static void Main(string[] args)
    {
        //int a=10;
        //change(ref a);
        //Console.WriteLine(a);
        // Console.Read();

        int b;
        change2(out b);
        Console.WriteLine(b);
        Console.Read();
    }
    // static void change(ref int a)
    //{
    //    a = 20;
    //}

     static void change2(out int b)
     {
         b = 20;
     }

あなたが "ref"を使うとき、あなたはそれがあなたにその完全な違いを説明するこのコードをチェックすることができますそのuはすでにそのint/stringを初期化するという意味

しかし、あなたが "out"を使うとき、それはどちらの条件でもうまくいきます。その場合、uはそのint/stringを初期化します。

1
Haris Zia

それらはほとんど同じです - 唯一の違いは、outパラメータとして渡す変数を初期化する必要がないことと、refパラメータを使用するメソッドがそれを何かに設定する必要があることです。

int x;    Foo(out x); // OK 
int y;    Foo(ref y); // Error

Refパラメータは変更される可能性のあるデータ用で、outパラメータはすでに何かの戻り値を使用している関数の追加出力であるデータ用です(例:int.TryParse)。

1
Talha Khan

パラメータを参照として渡す場合は、パラメータを関数に渡す前に初期化する必要があります。それ以外の場合はコンパイラ自体がエラーを表示します。ただし、outパラメータの場合は、オブジェクトパラメータを初期化する必要はありませんmethod.呼び出し元のメソッド自体でオブジェクトを初期化できます。

0
Rakeshkumar Das

以下に、 Ref out の両方を使用した例を示しました。今、あなたはすべてrefとoutについてクリアされます。

下記の例では、私はコメント // myRefObj = new myClass {Name = "ref外に呼ばれた!!"}; line、 "未割り当てローカル変数 'myRefObj'の使用" というエラーが出ますが、 out にそのようなエラーはありません。

Refの使用場所 :inパラメータを使用してプロシージャを呼び出すときに、同じprocの出力を格納するために同じパラメータが使用されます。

Outを使用する場所: inパラメータを指定せずに同じパラメータを使用してプロシージャを呼び出す場合は、そのprocから値を返すために使用されます。出力にも注意してください

public partial class refAndOutUse : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        myClass myRefObj;
        myRefObj = new myClass { Name = "ref outside called!!  <br/>" };
        myRefFunction(ref myRefObj);
        Response.Write(myRefObj.Name); //ref inside function

        myClass myOutObj;
        myOutFunction(out myOutObj);
        Response.Write(myOutObj.Name); //out inside function
    }

    void myRefFunction(ref myClass refObj)
    {
        refObj.Name = "ref inside function <br/>";
        Response.Write(refObj.Name); //ref inside function
    }
    void myOutFunction(out myClass outObj)
    {
        outObj = new myClass { Name = "out inside function <br/>" }; 
        Response.Write(outObj.Name); //out inside function
    }
}

public class myClass
{
    public string Name { get; set; }
} 
0
Ankur Bhutani

クラスの別のインスタンスに他の人の変数を再割り当てしたり、複数の値などを返したりできるようにするだけでなく、refまたはoutを使用すると、他の人があなたに必要なものとあなたが何をしようとしているかを知ることができます彼らが提供する変数

  • 必要なことはありません /refまたはout変更を行うだけの場合inside引数MyClassで渡されるsomeClassインスタンス。

    • 呼び出し元のメソッドは、refoutを使用するか、何も使用しないかにかかわらず、someClass.Message = "Hello World"などの変更を確認します
    • someClass = new MyClass()内にmyFunction(someClass)を書き込むと、someClassメソッドのスコープ内でのみmyFunctionによって認識されるオブジェクトがスワップアウトされます。呼び出し元のメソッドは、作成してメソッドに渡した元のMyClassインスタンスをまだ認識しています
  • まったく新しいオブジェクトのrefをスワップアウトする予定で、呼び出しメソッドに変更を表示させる場合は、 need outまたはsomeClass

    • someClass = new MyClass()内にmyFunction(out someClass)を書き込むと、myFunctionを呼び出したメソッドから見えるオブジェクトが変更されます

他のプログラマーが存在します

そして彼らはあなたが彼らのデータで何をしようとしているのか知りたいと思っています。あなたが何百万人もの開発者によって使用されるライブラリを書いていると想像してください。彼らはあなたのメソッドを呼び出すときに変数で何をしようとしているのかを彼らに知ってもらいたい

  • refを使用すると、「メソッドを呼び出すときに、ある値に割り当てられた変数を渡します。メソッドの途中で完全に別のものに変更する可能性があることに注意してください。変数が古いものを指すとは思わないでください」完了したらオブジェクト」

  • outを使用すると、「メソッドにプレースホルダー変数を渡します。値があるかどうかは関係ありません。コンパイラーは新しい値にそれを割り当てるように強制します。あなたが私のメソッドを呼び出す前のあなたの変数、will私が終わったときまでに異なる

ところで、C#7.2にはin修飾子もあります

そして、それはメソッドが渡されたインスタンスを別のインスタンスにスワップアウトすることを防ぎます。何百万人もの開発者に「元の変数参照を渡してください。慎重に作成されたデータを別のものに交換しないことを約束します」と言ってください。 inにはいくつかの特殊性があり、ショートをin intと互換性を持たせるために暗黙の変換が必要になる場合など、コンパイラは一時的にintを作成し、ショートを広げて参照で渡し、終了しますアップ。あなたはそれを台無しにしないと宣言したので、これを行うことができます。


Microsoftは、数値型の.TryParseメソッドを使用してこれを行いました。

int i = 98234957;
bool success = int.TryParse("123", out i);

パラメータにoutのフラグを立てることで、ここで積極的に宣言しています。「私たちはdefinitely 98234957の骨の折れる細工された値を他の何かに変更します」

もちろん、値型の解析などの場合は、解析メソッドが他の値型と値型を交換することを許可されなかった場合、うまく機能しないため、やらなければなりません。しかし、いくつかの架空のメソッドがあると想像してください作成しているライブラリ:

public void PoorlyNamedMethod(out SomeClass x)

これはoutであることがわかります。したがって、数字を計算するのに何時間も費やすと、完璧なSomeClassが作成されることがわかります。

SomeClass x = SpendHoursMakingMeAPerfectSomeClass();
//now give it to the library
PoorlyNamedMethod(out x);

まあ、それは時間の無駄でした、そのすべての時間をその完璧なクラスにするのにかかりました。それは間違いなく捨てられ、PoorlyNamedMethodに置き換えられます

0
Caius Jard

パラメータを受け取るメソッドの観点から見ると、refoutの違いは、C#はメソッドがすべてのoutパラメータに書き込む前に書き込みを行う必要があり、outパラメータとして渡す以外には何もしないことです。別のメソッドにoutパラメータとして渡されるか、直接書き込まれるまで、または書き込みます。他の言語ではそのような要件を課さないことに注意してください。 C#でoutパラメーターを使用して宣言されている仮想メソッドまたはインターフェース・メソッドは、そのようなパラメーターに特別な制限を課さない別の言語でオーバーライドされる可能性があります。

呼び出し側から見ると、outパラメーターを使用してメソッドを呼び出すと、渡された変数が最初に読み取られずに書き込まれるため、C#は多くの状況で想定します。他の言語で書かれたメソッドを呼び出すとき、この仮定は正しくないかもしれません。例えば:

struct MyStruct
{
   ...
   myStruct(IDictionary<int, MyStruct> d)
   {
     d.TryGetValue(23, out this);
   }
}

MyStruct s = new MyStruct(myDictionary);が代入のように見えても、myDictionaryがC#以外の言語で書かれたIDictionary<TKey,TValue>の実装を識別している場合は、潜在的にsが未変更のままになる可能性があります。

C#のコンストラクタとは異なり、VB.NETで作成されたコンストラクタは、呼び出されたメソッドがoutパラメータを変更し、すべてのフィールドを無条件で消去するかどうかについては仮定しません。上で暗示された奇妙な振る舞いは、完全にVBまたは完全にC#で書かれたコードでは起こりませんが、C#で書かれたコードがVB.NETで書かれたメソッドを呼び出すときに起こります。

0
supercat