関数でオブジェクトを変更できるように、オブジェクトを渡す必要がある場所に関数を作成しています。違いは何ですか:
public void myFunction(ref MyClass someClass)
そして
public void myFunction(out MyClass someClass)
どちらを使用すればよいですか、またその理由は何ですか。
ref
は、関数に入る前にオブジェクトが初期化されていることをコンパイラに伝えます。一方、out
は、オブジェクトが関数内で初期化されることをコンパイラに伝えます。
ref
は双方向ですが、out
はout-onlyです。
ref
修飾子は以下のことを意味します。
out
修飾子は以下のことを意味します。
DomがTPSレポートに関するメモについてPeterのブースに登場したとしましょう。
もしDomがrefの議論であれば、彼はそのメモのコピーを印刷していたでしょう。
もしDomが議論の余地がなかったら、彼は彼が彼と一緒に持っていくために彼にそのメモの新しいコピーを印刷させるでしょう。
私は説明を試みるつもりです:
値型がどのように正しく機能するのか理解していると思いますか。値の型は(int、long、structなど)です。 refコマンドなしでそれらを関数に送ると、 data がコピーされます。関数内でそのデータにあなたがしたことは、オリジナルにではなくコピーにのみ影響します。 refコマンドはACTUALデータを送信し、変更があると関数外のデータに影響します。
わかりにくい部分は、参照型です。
参照型を作成しましょう。
List<string> someobject = new List<string>()
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
refは と out にあります。
あなたの要求を満たすのに十分なところならどこでもout
を優先して使うべきです。
C#では、メソッドは1つの値しか返すことができません。複数の値を返したい場合は、outキーワードを使用できます。 out修飾子は、参照による戻り値として返されます。最も単純な答えは、メソッドから値を取得するためにキーワード「out」が使用されることです。
C#では、int、float、doubleなどの値型をメソッドパラメータの引数として渡すと、値によって渡されます。したがって、パラメータ値を変更しても、メソッド呼び出しの引数には影響しません。しかし、もしあなたが“ ref”キーワードでパラメータをマークすると、それは実際の変数に反映されます。
犬、猫の例を拡張する。 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;
}
参照型(クラス)を渡すので、デフォルトでは実際のオブジェクトへの 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
を使用する必要はありません。
ref
とout
は、以下の違いを除いて同様に動作します。
ref
変数は使用前に初期化する必要があります。代入なしでout
変数を使用できるout
パラメータは、それを使用する関数によって未割り当ての値として扱われる必要があります。そのため、呼び出し元のコードで初期化されたout
パラメータを使用できますが、関数が実行されると値は失われます。例によって学ぶ人たちのために(私のように)これは Anthony Kolesovが言っていることです 。
この点を説明するために、ref、outなどの最小限の例をいくつか作成しました。ベストプラクティスは扱っていません。違いを理解するための例にすぎません。
「パン屋」
それは、最初のものがあなたの文字列参照を "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クラスを使用するとパフォーマンスが向上することがよくあります)
ref は、refパラメータの値がすでに設定されていることを意味します。このメソッドは、値を読み取って変更できます。 refキーワードを使用することは、呼び出し側がパラメータの値を初期化する責任があるということと同じです。
out は、objectの初期化が関数の責任であることをコンパイラに伝えます。関数はoutパラメータに割り当てる必要があります。未割り当てのままにすることはできません。
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();
}
}
}
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);
オーサリング時間:
(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)を続けます。初期化されていないエラーはスローされず、以前に保存された参照はすべて失われます。
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は両者を区別しません)。
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を初期化します。
それらはほとんど同じです - 唯一の違いは、outパラメータとして渡す変数を初期化する必要がないことと、refパラメータを使用するメソッドがそれを何かに設定する必要があることです。
int x; Foo(out x); // OK
int y; Foo(ref y); // Error
Refパラメータは変更される可能性のあるデータ用で、outパラメータはすでに何かの戻り値を使用している関数の追加出力であるデータ用です(例:int.TryParse)。
パラメータを参照として渡す場合は、パラメータを関数に渡す前に初期化する必要があります。それ以外の場合はコンパイラ自体がエラーを表示します。ただし、outパラメータの場合は、オブジェクトパラメータを初期化する必要はありませんmethod.呼び出し元のメソッド自体でオブジェクトを初期化できます。
以下に、 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; }
}
クラスの別のインスタンスに他の人の変数を再割り当てしたり、複数の値などを返したりできるようにするだけでなく、ref
またはout
を使用すると、他の人があなたに必要なものとあなたが何をしようとしているかを知ることができます彼らが提供する変数
必要なことはありません /ref
またはout
変更を行うだけの場合inside引数MyClass
で渡されるsomeClass
インスタンス。
ref
、out
を使用するか、何も使用しないかにかかわらず、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私が終わったときまでに異なる
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に置き換えられます
パラメータを受け取るメソッドの観点から見ると、ref
とout
の違いは、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で書かれたメソッドを呼び出すときに起こります。