C# Language
値の型と参照の型
サーチ…
構文
- 参照渡し:public void Double(ref int numberToDouble){}
備考
前書き
値の種類
値の型は、2つのうちの単純なものです。値型は、データ自体を表すためによく使われます。 3D空間内の整数、ブール値、またはポイントは、すべて適切な値タイプの例です。
値型(構造体)は、structキーワードを使用して宣言されます。新しい構造体を宣言する方法の例については、構文セクションを参照してください。
一般に、値の型を宣言するために使用される2つのキーワードがあります。
- 構造
- 列挙型
参照型
参照型はやや複雑です。参照型は、オブジェクト指向プログラミングの意味での伝統的なオブジェクトです。したがって、継承(およびその利点)をサポートし、ファイナライザをサポートします。
C#では一般に次のような参照型があります。
- クラス
- 代表者
- インターフェイス
新しい参照型(クラス)は、classキーワードを使用して宣言されます。例については、新しい参照型を宣言する方法の構文セクションを参照してください。
主な違い
参照型と値型の主な違いを以下に示します。
値の型はスタックに存在し、参照型はヒープ上に存在します
これはしばしば言及される2つの違いですが、実際には、C#で値型を使用すると、プログラムはその変数を使用してその値を直接参照します。 int mine = 0と言うと、変数mineは0を直接参照して効率的です。しかし、参照型は、基本的なオブジェクトへの参照を実際に保持しています(名前が示唆するように)。これはC ++などの他の言語のポインタに似ています。
すぐにこれに気付かないかもしれませんが、効果はそこにあり、強力で微妙です。例については、参照型の変更例を参照してください。
この相違は、以下の他の相違の主な理由であり、知る価値があります。
値の型は、メソッド内で値を変更しても変更されません。
値の型がパラメータとしてメソッドに渡されたときに、メソッドが値を変更した場合、その値は変更されません。対照的に、同じ型のメソッドに参照型を渡し、それを変更すると、その同じオブジェクトを使用する他のものは、元の値ではなく新たに変更されたオブジェクトを持ちます。
詳細については、値型とメソッドの参照型の例を参照してください。
もし私がそれらを変更したいのですが?"ref"キーワードを使用してメソッドに渡すだけで、このオブジェクトを参照渡しします。意味、それはメモリ内の同じオブジェクトです。あなたが行う変更は尊重されます。例については、参照渡しの例を参照してください。
値の型をnullにすることはできません。
かなり言われるように、参照型にnullを割り当てることができます。つまり、割り当てた変数には実際のオブジェクトが割り当てられていないことを意味します。ただし、値型の場合、これは不可能です。しかし、これが必須であれば、値の型をnull値にできるようにNullableを使うことができますが、これがあなたの考えである場合は、クラスが最良のアプローチではないかどうかを強く考えるタイプ。
他の場所での値の変更
public static void Main(string[] args)
{
var studentList = new List<Student>();
studentList.Add(new Student("Scott", "Nuke"));
studentList.Add(new Student("Vincent", "King"));
studentList.Add(new Student("Craig", "Bertt"));
// make a separate list to print out later
var printingList = studentList; // this is a new list object, but holding the same student objects inside it
// oops, we've noticed typos in the names, so we fix those
studentList[0].LastName = "Duke";
studentList[1].LastName = "Kong";
studentList[2].LastName = "Brett";
// okay, we now print the list
PrintPrintingList(printingList);
}
private static void PrintPrintingList(List<Student> students)
{
foreach (Student student in students)
{
Console.WriteLine(string.Format("{0} {1}", student.FirstName, student.LastName));
}
}
printListリストがタイプミスの後に生徒名の訂正より前に作成されていても、PrintPrintingListメソッドは訂正された名前を出力します。
Scott Duke
Vincent Kong
Craig Brett
これは、両方のリストが同じ学生への参照リストを保持しているためです。基礎となる生徒のオブジェクトを変更することにより、いずれかのリストによって用途に伝播します。
生徒のクラスは次のようになります。
public class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Student(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
}
参照渡し
値型とメソッドの参照型の例が正しく動作するようにするには、参照渡しするパラメータとメソッドの呼び出し時に、メソッドのシグネチャでrefキーワードを使用します。
public static void Main(string[] args)
{
...
DoubleNumber(ref number); // calling code
Console.WriteLine(number); // outputs 8
...
}
public void DoubleNumber(ref int number)
{
number += number;
}
これらの変更を加えると、番号の更新が期待どおりに行われます。つまり、numberのコンソール出力は8になります。
refキーワードを使用して参照渡し。
ドキュメントから:
C#では、引数は値または参照のいずれかによってパラメータに渡すことができます。参照渡しにより、関数メンバ、メソッド、プロパティ、インデクサ、演算子、およびコンストラクタはパラメータの値を変更し、その変更を呼び出し環境で持続させることができます。参照でパラメーターを渡すには、
ref
またはout
キーワードを使用します。
差ref
及びout
つまりout
渡されたパラメータを用いて渡された関数ends.inコントラストパラメータ前に割り当てなければならないことを意味ref
変更または不変のままにすることができます。
using System;
class Program
{
static void Main(string[] args)
{
int a = 20;
Console.WriteLine("Inside Main - Before Callee: a = {0}", a);
Callee(a);
Console.WriteLine("Inside Main - After Callee: a = {0}", a);
Console.WriteLine("Inside Main - Before CalleeRef: a = {0}", a);
CalleeRef(ref a);
Console.WriteLine("Inside Main - After CalleeRef: a = {0}", a);
Console.WriteLine("Inside Main - Before CalleeOut: a = {0}", a);
CalleeOut(out a);
Console.WriteLine("Inside Main - After CalleeOut: a = {0}", a);
Console.ReadLine();
}
static void Callee(int a)
{
a = 5;
Console.WriteLine("Inside Callee a : {0}", a);
}
static void CalleeRef(ref int a)
{
a = 6;
Console.WriteLine("Inside CalleeRef a : {0}", a);
}
static void CalleeOut(out int a)
{
a = 7;
Console.WriteLine("Inside CalleeOut a : {0}", a);
}
}
出力 :
Inside Main - Before Callee: a = 20
Inside Callee a : 5
Inside Main - After Callee: a = 20
Inside Main - Before CalleeRef: a = 20
Inside CalleeRef a : 6
Inside Main - After CalleeRef: a = 6
Inside Main - Before CalleeOut: a = 6
Inside CalleeOut a : 7
Inside Main - After CalleeOut: a = 7
割り当て
var a = new List<int>();
var b = a;
a.Add(5);
Console.WriteLine(a.Count); // prints 1
Console.WriteLine(b.Count); // prints 1 as well
変数に代入List<int>
のコピーを作成しませんList<int>
。代わりに、参照をList<int>
にコピーします。このような参照型を動作させる型を呼び出します。
メソッドパラメータのrefとoutとの違い
参照で値型を渡すには、 ref
とout
2つの方法があります。違いは、 ref
を渡すことによって値を初期化する必要がありますが、それを渡すときではないout
です。 out
を使用すると、メソッド呼び出しの後に変数に値が設定されます。
public void ByRef(ref int value)
{
Console.WriteLine(nameof(ByRef) + value);
value += 4;
Console.WriteLine(nameof(ByRef) + value);
}
public void ByOut(out int value)
{
value += 4 // CS0269: Use of unassigned out parameter `value'
Console.WriteLine(nameof(ByOut) + value); // CS0269: Use of unassigned out parameter `value'
value = 4;
Console.WriteLine(nameof(ByOut) + value);
}
public void TestOut()
{
int outValue1;
ByOut(out outValue1); // prints 4
int outValue2 = 10; // does not make any sense for out
ByOut(out outValue2); // prints 4
}
public void TestRef()
{
int refValue1;
ByRef(ref refValue1); // S0165 Use of unassigned local variable 'refValue'
int refValue2 = 0;
ByRef(ref refValue2); // prints 0 and 4
int refValue3 = 10;
ByRef(ref refValue3); // prints 10 and 14
}
キャッチは、使用していることであるout
パラメータをmust
ので、以下の方法が持つことが可能であり、方法を離れる前に初期化することref
とではなく、 out
:
public void EmtyRef(bool condition, ref int value)
{
if (condition)
{
value += 10;
}
}
public void EmtyOut(bool condition, out int value)
{
if (condition)
{
value = 10;
}
} //CS0177: The out parameter 'value' must be assigned before control leaves the current method
これは、 condition
が成立しないと、 value
が割り当てられなくなるためです。
refとoutのパラメータ
コード
class Program
{
static void Main(string[] args)
{
int a = 20;
Console.WriteLine("Inside Main - Before Callee: a = {0}", a);
Callee(a);
Console.WriteLine("Inside Main - After Callee: a = {0}", a);
Console.WriteLine();
Console.WriteLine("Inside Main - Before CalleeRef: a = {0}", a);
CalleeRef(ref a);
Console.WriteLine("Inside Main - After CalleeRef: a = {0}", a);
Console.WriteLine();
Console.WriteLine("Inside Main - Before CalleeOut: a = {0}", a);
CalleeOut(out a);
Console.WriteLine("Inside Main - After CalleeOut: a = {0}", a);
Console.ReadLine();
}
static void Callee(int a)
{
a += 5;
Console.WriteLine("Inside Callee a : {0}", a);
}
static void CalleeRef(ref int a)
{
a += 10;
Console.WriteLine("Inside CalleeRef a : {0}", a);
}
static void CalleeOut(out int a)
{
// can't use a+=15 since for this method 'a' is not intialized only declared in the method declaration
a = 25; //has to be initialized
Console.WriteLine("Inside CalleeOut a : {0}", a);
}
}
出力
Inside Main - Before Callee: a = 20
Inside Callee a : 25
Inside Main - After Callee: a = 20
Inside Main - Before CalleeRef: a = 20
Inside CalleeRef a : 30
Inside Main - After CalleeRef: a = 30
Inside Main - Before CalleeOut: a = 30
Inside CalleeOut a : 25
Inside Main - After CalleeOut: a = 25