「ref」と「out」キーワードの違いは何ですか?
質問
関数によって変更できるようにオブジェクトを渡す必要がある関数を作成しています。違いは何ですか:
public void myFunction(ref MyClass someClass)
と
public void myFunction(out MyClass someClass)
どちらを使用すればよいですか?
解決
ref
コンパイラに、関数を入力する前にオブジェクトが初期化されていることを伝えます。 out
コンパイラに、オブジェクトが関数内で初期化されることを伝えます。
だから ref
双方向です、 out
アウトのみです。
他のヒント
ref
修飾子はそれを意味します:
- 値はすでに設定されています
- メソッドはそれを読み取り、変更できます。
out
修飾子はそれを意味します:
- 値は設定されておらず、メソッドで読むことができません それまで それが設定されています。
- メソッド しなければならない 戻る前に設定します。
DomがTPSレポートに関するメモについてPeterのキュービクルに表示されるとしましょう。
DOMがREFの引数である場合、彼はメモの印刷されたコピーを持っています。
ドムがアウトの議論だった場合、彼はピーターを彼と一緒に取るためのメモの新しいコピーを印刷させるでしょう。
説明で手を試してみるつもりです:
私たちは価値の種類がどのように機能するかを理解していると思いますか?値タイプは(int、long、structなど)です。 refコマンドなしでそれらを関数に送信すると、 データ. 。関数のそのデータに対してあなたがすることはすべて、コピーにのみ影響し、元のものではありません。 REFコマンドは実際のデータを送信し、変更は関数以外のデータに影響します。
混乱している部分にOK、参照タイプ:
参照タイプを作成しましょう。
List<string> someobject = new List<string>()
あなたが新しいとき SomeObject, 、2つの部分が作成されます。
- データを保持するメモリのブロック SomeObject.
- データブロックへの参照(ポインター)。
今、あなたが送るとき SomeObject refのない方法にコピーします 参照 データではなくポインター。だからあなたは今これを持っています:
(outside method) reference1 => someobject
(inside method) reference2 => someobject
同じオブジェクトを指す2つの参照。プロパティを変更した場合 SomeObject Reference2を使用すると、Reference1によって指された同じデータに影響します。
(inside method) reference2.Add("SomeString");
(outside method) reference1[0] == "SomeString" //this is true
Reference2をnultしたり、新しいデータを指したりすると、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
今、あなたが送るときに何が起こるか SomeObject メソッドを参照しますか? 実際の参照 に SomeObject メソッドに送信されます。そのため、データへの参照は1つだけです。
(outside method) reference1 => someobject;
(inside method) reference1 => someobject;
しかし、これはどういう意味ですか? 2つの主要なものを除いて、refではないObjectを送信するのとまったく同じように動作します。
1)メソッド内の参照をnultすると、メソッドの外側のものを拒否します。
(inside method) reference1 = null;
(outside method) reference1 == null; //true
2)参照をまったく異なるデータの位置に向けることができ、関数の外側の参照が新しいデータの場所を指すことができます。
(inside method) reference1 = new List<string>();
(outside method) reference1.Count == 0; //this is true
使用する必要があります out
あなたの要件に十分な場所で優先されます。
アウト:
C#では、メソッドは1つの値のみを返すことができます。複数の値を返したい場合は、OUTキーワードを使用できます。 Returnごとに変更されたモディファイアが返されます。最も簡単な答えは、キーワード「out」がメソッドから値を取得するために使用されることです。
- 呼び出し関数の値を初期化する必要はありません。
- 呼び出された関数に値を割り当てる必要があります。そうしないと、コンパイラがエラーを報告します。
参照:
C#では、メソッドパラメーターの引数として、int、float、doubleなどの値タイプを渡すと、値によって渡されます。したがって、パラメーター値を変更すると、メソッド呼び出しの引数には影響しません。ただし、パラメーターを「ref」キーワードでマークすると、実際の変数に反映されます。
- 関数を呼び出す前に、変数を初期化する必要があります。
- メソッドのREFパラメーターに値を割り当てることは必須ではありません。値を変更しない場合、それを「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;
}
参照タイプ(クラス)を渡すので、使用する必要はありません 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
呼び出しコードのパラメーターですが、関数が実行されると値は失われます。
例で学んだ人(私のように)のためにこれが何ですか アンソニー・コレスフは言っています.
Ref、Outなどの最小限の例を作成して、ポイントを説明しました。私はベストプラクティスをカバーしていません。違いを理解するための例だけです。
「ベイカー」
それは、最初のものがあなたの文字列の参照を「ベイカー」に指すように変更するためです。参照を変更することは、REFキーワード(=>文字列への参照への参照)を介して渡したために可能です。 2番目の呼び出しは、文字列への参照のコピーを取得します。
文字列は最初はある種の特別に見えます。しかし、文字列は単なる参照クラスであり、定義する場合
string s = "Able";
次に、sは「有能」テキストを含む文字列クラスへの参照です!同じ変数を介して別の割り当て
s = "Baker";
元の文字列を変更するのではなく、新しいインスタンスを作成して、そのインスタンスを指し示します!
次の小さなコードの例で試すことができます。
string s = "Able";
string s2 = s;
s = "Baker";
Console.WriteLine(s2);
何を期待しますか? S2が元のインスタンスを指している間、別のインスタンスに参照を設定するだけなので、あなたが得るものはまだ「能力がある」ことです。
編集:文字列も不変です。つまり、既存の文字列インスタンスを変更する方法やプロパティはありません(ドキュメントで見つけようとすることはできますが、:-)はありません)。すべての文字列操作方法新しい文字列インスタンスを返します! (だからこそ、StringBuilderクラスを使用すると、パフォーマンスが向上します。)
ref REFパラメーターの値が既に設定されていることを意味します。メソッドはそれを読み取り、変更できます。 REFキーワードを使用することは、発信者がパラメーターの値の初期化を担当していると言うのと同じです。
アウト コンパイラに、オブジェクトの初期化が関数の責任であり、関数がOUTパラメーターに割り当てる必要があることを伝えます。割り当てされていないままにすることは許可されていません。
外: 返品ステートメントは、関数から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();
}
}
}
参照:参照パラメーターは、変数のメモリ位置への参照です。参照によってパラメーターを渡す場合、値パラメーターとは異なり、これらのパラメーターに新しいストレージ場所は作成されません。参照パラメーターは、メソッドに提供される実際のパラメーターと同じメモリの位置を表します。
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();
}
}
}
参照とアウトは、参照を通過し、C ++のようにポインターで通過するように機能します。
参照の場合、引数は宣言され、初期化されなければなりません。
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)リストオブジェクト(参照型オブジェクト)を作成し、変数に保存します myList
.
public sealed class Program
{
public static Main()
{
List<int> myList = new List<int>();
ランタイム中:
(3)ランタイムは、住所を保存するのに十分な幅の#00にスタックのメモリを割り当てます(#00 = myList
, 、変数名は本当にメモリロケーションのエイリアスにすぎないため)
(4)ランタイムは、メモリの位置#ffでヒープにリストオブジェクトを作成します(これらのアドレスはすべてサークスです)
(5)ランタイムは、オブジェクトの開始アドレス#ffを#00に保存します(または言葉で、リストオブジェクトの参照をポインターに保存します myList
)
オーサリング時間に戻る:
(6)次に、リストオブジェクトを引数として渡す myParamList
呼び出された方法に modifyMyList
新しいリストオブジェクトを割り当てます
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をこの値に置き換えます(または、元のリストオブジェクトを再参入し、このメソッドの新しいリストオブジェクトを指しています)
#00のアドレスは変更されておらず、#ff(またはオリジナルの参照を保持しています myList
ポインターは邪魔されません)。
ref キーワードは、(8)および(9)のランタイムコードの生成をスキップするコンパイラディレクティブであり、メソッドパラメーターにヒープ割り当てがないことを意味します。元の#00ポインターを使用して、#FFのオブジェクトで動作します。元のポインターが初期化されていない場合、変数が初期化されていないため、実行時間が停止することができません。
アウト キーワードは、(9)および(10)でわずかな変更を加えたREFとほぼ同じコンパイラディレクティブです。コンパイラは、引数が無知であると予想しており、(8)、(4)、および(5)を継続して、ヒープ上でオブジェクトを作成し、その開始アドレスを引数変数に保存します。未解決のエラーはスローされず、保存された以前の参照は失われます。
それらはほとんど同じです - 唯一の違いは、OUTパラメーターとして渡す変数を初期化する必要がないこと、そしてREFパラメーターを使用した方法はそれを何かに設定する必要があることです。
int x; Foo(out x); // OK
int y; Foo(ref y); // Error
REFパラメーターは、変更される可能性のあるデータのものであり、アウトパラメーターは、すでに何かに対して戻り値を使用している関数(int.tryparseなど)の追加の出力であるデータのものです。
以下に、両方を使用した例を示しました ref と アウト. 。これで、皆さんはrefとoutについてクリアされます。
私がコメントするとき、下記の例では // myrefobj = new myclass {name = "ref outsident call !!"};ライン、エラーが発生します 「未割り当てのローカル変数の使用「myrefobj」」, 、しかし、そのようなエラーはありません アウト.
Refを使用する場所: :INパラメーターを使用して手順を呼び出している場合、同じパラメーターを使用してそのProcの出力を保存します。
使用する場所: パラメーターがない手順を呼び出している場合、同じパラメーターを使用してその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; }
}
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」を使用すると、その完全な違いを説明します。
ただし、「out」を使用する場合、両方の条件で機能しますuはそのint/stringかどうかを初期化しますが、その関数でそのint/stringを初期化する必要があります
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は実行時に異なって扱われますが、コンパイル時に同じ扱いがあります(CLRは2つを区別しませんが、ILをREFとアウトのために作成します)。
パラメーターを受信するメソッドの観点から、間の違い ref
と out
C#は、方法がすべてに書き込む必要があることを要求していますか out
戻る前にパラメーター、そしてそれを渡す以外に、そのようなパラメーターで何もしてはならない out
パラメーターまたはそれが渡されるまで、それに書き込み out
別のメソッドへのパラメーターまたは直接書き込まれます。他のいくつかの言語はそのような要件を課していないことに注意してください。 c#で宣言されている仮想またはインターフェイスメソッド out
パラメーターは、そのようなパラメーターに特別な制限を課さない別の言語でオーバーライドされる場合があります。
発信者の観点から、c#は多くの状況で、メソッドをで呼び出すときに想定します out
パラメーターは、最初に読み取られずに通過した変数を記述します。この仮定は、他の言語で書かれた方法を呼び出すときに正しくない場合があります。例えば:
struct MyStruct
{
...
myStruct(IDictionary<int, MyStruct> d)
{
d.TryGetValue(23, out this);
}
}
もしも myDictionary
識別します IDictionary<TKey,TValue>
C#以外の言語で書かれた実装 MyStruct s = new MyStruct(myDictionary);
割り当てのように見えますが、潜在的に離れる可能性があります s
変更されていない。
vb.netで書かれたコンストラクターは、C#のコンストラクターとは異なり、呼び出されたメソッドがいずれかを変更するかどうかについて仮定しないことに注意してください out
パラメーター、およびすべてのフィールドを無条件にクリアします。上記で暗示されている奇妙な動作は、VBまたは完全にC#で完全に記述されたコードでは発生しませんが、C#で書かれたコードがVB.NETで記述されたメソッドを呼び出すと発生する可能性があります。
パラメーターを参照として渡す場合は、パラメーターを関数に渡す前にパラメーターを初期化する必要があります。方法。呼び出しメソッド自体のオブジェクトを初期化できます。
他の人の変数をクラスの別のインスタンスに再割り当てしたり、複数の値を返したりできるようにすることを可能にします。 使用 ref
また out
他の誰かにあなたが彼らに何を必要とし、あなたが彼らが提供する変数で何をするつもりなのかを知らせましょう
君は 必要はありません
ref
またout
あなたがやろうとしているなら、物事を変更することだけです 中身MyClass
引数で渡されるインスタンスsomeClass
.- 呼び出し方は、次のような変更が表示されます
someClass.Message = "Hello World"
使用するかどうかref
,out
または何もありません - 書き込み
someClass = new MyClass()
中身myFunction(someClass)
に見えるオブジェクトを交換しますsomeClass
の範囲内myFunction
メソッドのみ。呼び出し方はまだ元のことを知っていますMyClass
たとえば、作成してメソッドに渡されます
- 呼び出し方は、次のような変更が表示されます
君は 必要
ref
またout
スワッピングを計画している場合someClass
まったく新しいオブジェクトのために出て、呼び出し方式があなたの変化を確認することを望んでいます- 書き込み
someClass = new MyClass()
中身myFunction(out someClass)
呼び出された方法で見られるオブジェクトを変更しますmyFunction
- 書き込み
他のプログラマーが存在します
そして、彼らはあなたが彼らのデータで何をするつもりなのか知りたいと思っています。何百万人もの開発者が使用するライブラリを書いていると想像してください。あなたは彼らがあなたの方法を呼び出すときにあなたが彼らの変数で何をするかを彼らに知ってほしい
使用
ref
「私の方法を呼び出すときに何らかの値に割り当てられた変数を渡すことができます。私の方法の過程で、他の何かのためにそれを完全に変更するかもしれないことに注意してください。あなたの変数が古いオブジェクトを指していることを期待しないでください私はこれで終わりです"使用
out
「プレースホルダー変数を私のメソッドに合格します。値があるかどうかは関係ありません。コンパイラはそれを新しい値に割り当てるように強制します。オブジェクトが変数によって指されたことを絶対に保証します。あなたが私の方法を呼ぶ前に、 意思 私が終わった頃には違うことになります
ちなみに、C#7.2には in
修飾子も
そして、それは方法が別のインスタンスのためにインスタンスでパスを交換するのを防ぎます。何百万人もの開発者に「元の変数の参照を渡してください。慎重に作成されたデータを他の何かに交換しないことを約束します」と言ってください。 in
いくつかの特異性があり、場合によっては、短い互換性を持つために暗黙の変換が必要になる場合がある場合など、 in int
コンパイラは一時的にINTを作成し、それに短くし、参照で渡して仕上げます。あなたがそれを台無しにするつもりはないと宣言したので、それはこれを行うことができます。
マイクロソフトはこれを行いました .TryParse
数値タイプの方法:
int i = 98234957;
bool success = int.TryParse("123", out i);
パラメーターにフラグを立てます out
彼らはここで積極的に宣言しています 絶対 他の何かのために98234957の骨の折れる価値のある価値を変えるつもりです」
もちろん、ペルシングの種類のようなもののために、ペルセの方法が他の何かのために値のタイプを交換することを許可されていなかった場合、それはあまりうまくいかないので、彼らはちょっとする必要があります。あなたが作成しているライブラリ:
public void PoorlyNamedMethod(out SomeClass x)
あなたはそれがandであることがわかります out
, 、したがって、数時間を計算するのに何時間も費やしている場合、完璧なソムレスを作成することを知ることができます。
SomeClass x = SpendHoursMakingMeAPerfectSomeClass();
//now give it to the library
PoorlyNamedMethod(out x);
まあ、それは時間の無駄であり、それらの時間をすべて完璧なクラスにするために時間をかけました。それは間違いなく捨てられ、貧しい人々に置き換えられるでしょう
関数内で渡される参照パラメーターが直接動作することに注意してください。
例えば、
public class MyClass
{
public string Name { get; set; }
}
public void Foo()
{
MyClass myObject = new MyClass();
myObject.Name = "Dog";
Bar(myObject);
Console.WriteLine(myObject.Name); // Writes "Dog".
}
public void Bar(MyClass someObject)
{
MyClass myTempObject = new MyClass();
myTempObject.Name = "Cat";
someObject = myTempObject;
}
これは猫ではなく犬を書きます。したがって、Objectで直接作業する必要があります。
私はこれがそれほど得意ではないかもしれませんが、確かに文字列(技術的に参照タイプであり、ヒープ上でライブであっても)は、参照ではなく価値で渡されますか?
string a = "Hello";
string b = "goodbye";
b = a; //attempt to make b point to a, won't work.
a = "testing";
Console.WriteLine(b); //this will produce "hello", NOT "testing"!!!!
これが、関数を作成する機能の範囲外に変更を存在させたい場合にrefが必要な理由で、それ以外の場合は参照を渡すことはありません。
私が知る限り、文字列は値タイプではない参照タイプであるため、struct/valueタイプと文字列自体のrefのみが必要です。
私はここで完全に間違っているかもしれませんが、私は新しいです。