私は、関数によって変更できるようにオブジェクトを渡す必要がある関数を作成しています。違いは何ですか:
public void myFunction(ref MyClass someClass)
そして
public void myFunction(out MyClass someClass)
どちらを使用する必要がありますか?その理由は?
私は、関数によって変更できるようにオブジェクトを渡す必要がある関数を作成しています。違いは何ですか:
public void myFunction(ref MyClass someClass)
そして
public void myFunction(out MyClass someClass)
どちらを使用する必要がありますか?その理由は?
回答:
ref
関数に入る前にオブジェクトが初期化されることをout
コンパイラーに伝え、オブジェクトが関数内で初期化されることをコンパイラーに伝えます。
ですから、ref
双方向ですが、out
アウトのみです。
ref
修飾子手段という。
out
修飾子手段という。
out
、メソッドが呼び出される前に初期化されていた場合、そのメソッドによって設定される前に、メソッド内でそれを読み取ることはできますか?つまり、呼び出されたメソッドは、呼び出したメソッドが引数としてそれに渡したものを読み取ることができますか?
ドムがTPSレポートに関するメモについてピーターのキュービクルに現れたとしましょう。
ドムが参照論であるならば、彼はメモの印刷されたコピーを持っているでしょう。
ドムが反対論だった場合、彼はピーターに彼と一緒に持って行くためにメモの新しいコピーを印刷させるでしょう。
説明で手を試してみましょう:
値の型がどのように正しく機能するかを理解していると思いますか?値のタイプは(int、long、structなど)です。refコマンドなしでそれらを関数に送信すると、データがコピーされます。関数でそのデータに対して行うことは、コピーにのみ影響し、元には影響しません。refコマンドはACTUALデータを送信し、変更は関数外のデータに影響します。
わかりにくい部分に言及しましょう。参照型:
参照型を作成しましょう:
List<string> someobject = new List<string>()
someobjectを新規作成すると、2つの部分が作成されます。
ここで、refなしでsomeobjectをメソッドに送信すると、データではなく参照ポインターがコピーされます。だからあなたは今これを持っています:
(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をnullにするか、それを新しいデータにポイントしても、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
メソッドへの参照によってsomeobjectを送信するとどうなりますか?someobjectへの実際の参照がメソッドに送信されます。これで、データへの参照は1つだけになりました。
(outside method) reference1 => someobject;
(inside method) reference1 => someobject;
しかし、これはどういう意味ですか?これは、2つの主なことを除いて、refではなくsomeobjectを送信するのとまったく同じように動作します。
1)メソッド内の参照をnullにすると、メソッド外の参照がnullになります。
(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
キーワードと同じように説明できますか?
refはインとアウトです。
out
要件を満たすのに十分な場所で優先的に使用する必要があります。
C#では、メソッドは1つの値のみを返すことができます。複数の値を返したい場合は、outキーワードを使用できます。out修飾子は、参照渡しとして返されます。最も簡単な答えは、メソッドから値を取得するためにキーワード「out」が使用されることです。
C#では、int、float、doubleなどの値の型を引数としてメソッドパラメータに渡すと、値によって渡されます。したがって、パラメーター値を変更しても、メソッド呼び出しの引数には影響しません。ただし、パラメーターを「ref」キーワードでマークすると、実際の変数に反映されます。
Dog、Catの例を拡張します。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
メソッド内のオブジェクトを変更する場合は、クラスを渡す限り、使用する必要はありません。
someObject = null
をBar
終了するために追加します。Bar
インスタンスへのの参照のみがnullになったため、コードは正常に実行されます。次に、に変更Bar
しBar(ref MyClass someObject)
て再度実行します。インスタンスへのの参照もnullになっているNullReferenceException
ため、を取得しFoo
ます。
例で学ぶ人(私のように)は、Anthony Kolesovが言っていることをここに示します。
ポイントを説明するために、ref、outなどの最小限の例をいくつか作成しました。ここでは、ベストプラクティスについては取り上げません。違いを理解するための例にすぎません。
「パン屋」
これは、最初の文字列が文字列参照を「Baker」を指すように変更するためです。refキーワード(=>文字列への参照への参照)を介して渡したため、参照の変更が可能です。2番目の呼び出しは、文字列への参照のコピーを取得します。
文字列は、最初はある種の特別なものに見えます。しかし、文字列は単なる参照クラスであり、定義すると
string s = "Able";
次に、sは「Able」というテキストを含む文字列クラスへの参照です。同じ変数への別の割り当て
s = "Baker";
元の文字列は変更せず、新しいインスタンスを作成して、そのインスタンスを指すようにします。
次の小さなコード例でそれを試すことができます:
string s = "Able";
string s2 = s;
s = "Baker";
Console.WriteLine(s2);
何を期待していますか?s2が元のインスタンスを指している間、sの参照を別のインスタンスに設定するだけなので、取得できるのは依然として「Able」です。
編集:文字列も不変です。つまり、既存の文字列インスタンスを変更するメソッドまたはプロパティがないだけです(ドキュメント内で文字列を検索することはできますが、検索はできません:-))。すべての文字列操作メソッドは新しい文字列インスタンスを返します!(そのため、StringBuilderクラスを使用するとパフォーマンスが向上することがよくあります)
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: 参照パラメーターは、変数のメモリ位置への参照です。値パラメーターとは異なり、パラメーターを参照で渡す場合、これらのパラメーターの新しい保管場所は作成されません。参照パラメーターは、メソッドに提供される実際のパラメーターと同じメモリ位置を表します。
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);
out double Half_nbr
。
作成時間:
(1)呼び出しメソッドを作成する Main()
(2)リストオブジェクト(参照型オブジェクト)を作成し、変数に格納しますmyList
。
public sealed class Program
{
public static Main()
{
List<int> myList = new List<int>();
実行時:
(3)ランタイムは、アドレスを格納するのに十分な幅で、#00にスタック上のメモリを割り当てます(myList
変数名は実際にはメモリロケーションの単なるエイリアスであるため、#00 = )
(4)ランタイムはヒープ上のリストオブジェクトをメモリ位置#FFに作成します(これらのアドレスはすべて例のためです)
(5)次に、ランタイムはオブジェクトの開始アドレス#FFを#00に格納します(つまり、Listオブジェクトの参照をポインターに格納しますmyList
)
オーサリング時間に戻る:
(6)次にmyParamList
、呼び出されたメソッドに引数としてListオブジェクトを渡し、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をこの値で置き換えます(または、元のリストオブジェクトを逆参照し、このメソッドで新しいリストオブジェクトをポイントします)
#00のアドレスは変更されず、#FFへの参照を保持します(または元のmyList
ポインターは影響を受けません)。
REFのキーワードは、メソッドのパラメータのためのヒープ割り当てがないことを意味する(8)及び(9)のランタイムコードの生成をスキップするコンパイラディレクティブです。オリジナルの#00ポインターを使用して、#FFにあるオブジェクトを操作します。元のポインターが初期化されていない場合、変数は初期化されていないため、ランタイムはそれが先に進めないと警告します。
アウトキーワードはかなり(9)及び(10)にわずかな変更でREFと同じであるコンパイラ・ディレクティブです。コンパイラーは、引数が初期化されていないことを期待し、(8)、(4)、および(5)を続行して、ヒープ上にオブジェクトを作成し、その開始アドレスを引数変数に格納します。初期化されていないエラーはスローされず、保存されている以前の参照は失われます。
他の誰かの変数をクラスの別のインスタンスに再割り当てしたり、複数の値を返したりすることができるほか、他の誰かがそれらから必要なものを提供したり、提供した変数で何をしたいのかを知らせたりすることができます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
すると、「プレースホルダー変数をメソッドに渡します。値があるかどうかは関係ありません。コンパイラーはそれを新しい値に割り当てることを強制します。私は、あなたは私のメソッドを呼び出し、変数の前に、ます、私は終わりだ、時間によって異なること
in
修飾子もありますそして、それによって、メソッドが、渡されたインスタンスを別のインスタンスにスワップアウトするのを防ぎます。何百万人もの開発者に「元の変数参照を渡してください。細心の注意を払って作成したデータを他のものに交換しないことを約束します」と言っているようなものだと考えてください。in
にはいくつかの特徴がありin int
、コンパイラとの互換性を確保するために暗黙の変換が必要になる場合など、場合によっては一時的にintを作成し、shortをそれに広げ、参照によって渡して仕上げます。あなたはそれを台無しにするつもりはないと宣言したので、これを行うことができます。
Microsoftはこれを.TryParse
数値型のメソッドで行いました。
int i = 98234957;
bool success = int.TryParse("123", out i);
out
彼らがここで積極的に宣言しているときにパラメーターにフラグを設定することにより、「私たちは間違いなく細心の注意を払って作成された98234957の値を他のものに変更します」
もちろん、値の型の解析などでは、値の型を別のものにスワップアウトすることが許可されていなかった場合、うまく機能しないため、それらは少し必要になります。しかし、一部の架空のメソッドが作成しているライブラリ:
public void PoorlyNamedMethod(out SomeClass x)
あなたはそれout
がであることがわかります。したがって、数を計算するのに何時間も費やした場合、完璧なSomeClassを作成していることがわかります。
SomeClass x = SpendHoursMakingMeAPerfectSomeClass();
//now give it to the library
PoorlyNamedMethod(out x);
まあ、それは時間の無駄でした、その完璧なクラスを作るためにそれらすべての時間を費やしました。間違いなく破棄され、PoorlyNamedMethodに置き換えられます
多くの優れた説明を説明するために、次のコンソールアプリを開発しました。
using System;
using System.Collections.Generic;
namespace CSharpDemos
{
class Program
{
static void Main(string[] args)
{
List<string> StringList = new List<string> { "Hello" };
List<string> StringListRef = new List<string> { "Hallo" };
AppendWorld(StringList);
Console.WriteLine(StringList[0] + StringList[1]);
HalloWelt(ref StringListRef);
Console.WriteLine(StringListRef[0] + StringListRef[1]);
CiaoMondo(out List<string> StringListOut);
Console.WriteLine(StringListOut[0] + StringListOut[1]);
}
static void AppendWorld(List<string> LiStri)
{
LiStri.Add(" World!");
LiStri = new List<string> { "¡Hola", " Mundo!" };
Console.WriteLine(LiStri[0] + LiStri[1]);
}
static void HalloWelt(ref List<string> LiStriRef)
{ LiStriRef = new List<string> { LiStriRef[0], " Welt!" }; }
static void CiaoMondo(out List<string> LiStriOut)
{ LiStriOut = new List<string> { "Ciao", " Mondo!" }; }
}
}
/*Output:
¡Hola Mundo!
Hello World!
Hallo Welt!
Ciao Mondo!
*/
AppendWorld
:StringList
named のコピーLiStri
が渡されました。メソッドの開始時に、このコピーは元のリストを参照するため、このリストを変更するために使用できます。後でメソッド内のLiStri
別のList<string>
オブジェクトを参照しますが、元のリストには影響しません。
HalloWelt
:LiStriRef
はすでに初期化されているのエイリアスです
ListStringRef
。渡されたList<string>
オブジェクトは新しいオブジェクトを初期化するために使用されるため、ref
必要でした。
CiaoMondo
:LiStriOut
はのエイリアスでListStringOut
あり、初期化する必要があります。
したがって、メソッドが渡された変数によって参照されるオブジェクトを変更するだけの場合、コンパイラーは使用out
を許可せず、コンパイラーではなくref
コードのリーダーを混乱させるため、使用しないでください。メソッドが渡された引数が別のオブジェクトを参照するようにする場合はref
、既に初期化されているオブジェクトとout
、渡された引数に対して新しいオブジェクトを初期化する必要があるメソッドに使用します。それに加えて、ref
とout
同じように動作。
以下に、Refとoutの両方を使用した例を示します。今、あなたはすべてrefとoutについてクリアされます。
下記の例でコメントすると// myRefObj = new myClass {Name = "ref outside called !!"}; 「未割り当てのローカル変数 '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; }
}
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」を使用したときの完全な違いを説明できます。これは、そのint / stringがすでに初期化されていることを意味します
しかし、 "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は実行時に異なる方法で処理されますが、コンパイル時には同じように処理されます(CLRは、refとoutのILを作成している間、2つを区別しません)。
パラメータを受け取る方法の観点から、との違いref
とは、out
C#は、方法は、すべてに書かなければならないことを必要とすることであるout
戻す前に、パラメータ、およびとして渡す以外に、このようなパラメータで何もしてはならないout
ことへのパラメータまたは書き込み、out
パラメータとして別のメソッドに渡されるか、直接書き込まれるまで。他のいくつかの言語はそのような要件を課さないことに注意してください。out
パラメータを使用してC#で宣言された仮想メソッドまたはインターフェイスメソッドは、そのようなパラメータに特別な制限を課さない別の言語でオーバーライドできます。
呼び出し元の観点から、C#は多くの状況で、out
パラメーターを使用してメソッドを呼び出すと、渡された変数が最初に読み取られずに書き込まれると想定します。他の言語で記述されたメソッドを呼び出す場合、この仮定は正しくない場合があります。例えば:
struct MyStruct
{
...
myStruct(IDictionary<int, MyStruct> d)
{
d.TryGetValue(23, out this);
}
}
C#以外の言語で記述されmyDictionary
たIDictionary<TKey,TValue>
実装を識別する場合MyStruct s = new MyStruct(myDictionary);
、割り当てのように見えても、s
変更されないままになる可能性があります。
C#のコンストラクターとは異なり、VB.NETで作成されたコンストラクターは、呼び出されたメソッドがout
パラメーターを変更するかどうかを想定せず、すべてのフィールドを無条件にクリアすることに注意してください。上記で言及した奇妙な動作は、完全にVBまたはC#で記述されたコードでは発生しませんが、C#で記述されたコードがVB.NETで記述されたメソッドを呼び出すと発生する可能性があります。
関数内で渡される参照パラメーターが直接処理されることに注意してください。
例えば、
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;
}
これはCatではなくDogと書きます。したがって、someObjectを直接操作する必要があります。
私はこれがあまり得意ではないかもしれませんが、確かに文字列(技術的には参照型であり、ヒープ上に存在します)は参照ではなく値で渡されますか?
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が必要ですが、それ以外の場合は参照を渡しません。
私が知っている限り、構造体/値型と文字列自体の参照のみが必要です。文字列は、それを偽る参照型であるため、値型ではありません。
ここで私は完全に間違っているかもしれません、私は新しいです。
Capitalize()
に、文字列の内容を大文字に変更するメソッドが呼び出されたとします。その後、行a = "testing";
をa.Capitalize();
に置き換えた場合、出力は「Hello」ではなく「HELLO」になります。不変タイプの利点の1つは、参照を渡すことができ、他のコードが値を変更する心配がないことです。
MyClass
、class
タイプ、つまり参照タイプのように見えます。その場合、渡すキーワードmyFunction
はref
/out
キーワードを付けなくても変更できます。myFunction
は同じオブジェクトを指す新しい参照を受け取り、同じオブジェクトを必要なだけ変更できます。違いref
キーワードになるだろうが、そのなりmyFunction
受信同じ同じオブジェクトへの参照。これはmyFunction
、別のオブジェクトを指すように参照を変更する場合にのみ重要です。