宣言ステートメント
宣言ステートメントでは、新しいローカル変数、ローカル定数、または ref ローカル変数を宣言します。 ローカル変数を宣言するには、その型とその名前を指定します。 次の例に示すように、1 つのステートメントで同じ型の複数の変数を宣言できます。
string greeting;
int a, b, c;
List<double> xs;
宣言ステートメントでは、変数をその初期値で初期化することもできます。
string greeting = "Hello";
int a = 3, b = 2, c = a + b;
List<double> xs = new();
前の例では、変数の型を明示的に指定しています。 また、コンパイラに初期化式から変数の型を推論させることができます。 これを行うには、型の名前の代わりに var
キーワードを使用します。 詳細については、「暗黙的に型指定されたローカル変数」セクションを参照してください。
ローカル定数を宣言するには、次の例に示すように、const
キーワードを使用します。
const string Greeting = "Hello";
const double MinLimit = -10.0, MaxLimit = -MinLimit;
ローカル定数を宣言するときは、それを初期化する必要もあります。
ref ローカル変数の詳細については、「ref ローカル変数」セクションを参照してください。
暗黙的に型指定されたローカル変数
ローカル変数を宣言するときに、コンパイラに初期化式から変数の型を推論させることができます。 これを行うには、型の名前の代わりに var
キーワードを使用します。
var greeting = "Hello";
Console.WriteLine(greeting.GetType()); // output: System.String
var a = 32;
Console.WriteLine(a.GetType()); // output: System.Int32
var xs = new List<double>();
Console.WriteLine(xs.GetType()); // output: System.Collections.Generic.List`1[System.Double]
前の例に示すように、暗黙的に型指定されたローカル変数は厳密に型指定されます。
注意
有効な Null 許容認識コンテキスト で var
を使用し、初期化式の型が参照型である場合、コンパイラでは、初期化式の型が Null 許容でない場合でも、常に Null 許容 参照型を推論します。
var
は、コンストラクターの呼び出し式と共に使用するのが一般的です。 var
を使用すると、次の例に示すように、変数宣言およびオブジェクトのインスタンス化において型名を繰り返す必要がなくなります。
var xs = new List<int>();
C# 9.0 以降では、代替としてターゲット型の new
式を使用できます。
List<int> xs = new();
List<int>? ys = new();
匿名型を使う場合は、暗黙的に型指定されたローカル変数を使用する必要があります。 次の例は、匿名型を使用して顧客の名前と電話番号を保持するクエリ式を示しています。
var fromPhoenix = from cust in customers
where cust.City == "Phoenix"
select new { cust.Name, cust.Phone };
foreach (var customer in fromPhoenix)
{
Console.WriteLine($"Name={customer.Name}, Phone={customer.Phone}");
}
前の例では、fromPhoenix
変数の型を明示的に指定することはできません。 型は IEnumerable<T> ですが、この場合、T
は匿名型であり、その名前を指定することはできません。 そのため、var
を使用する必要があります。 同じ理由から、foreach
ステートメントで customer
反復変数を宣言するときは、var
を使用する必要があります。
暗黙的に型指定されたローカル変数の詳細については、「暗黙的に型指定されたローカル変数」を参照してください。
パターン マッチングでは、var
キーワードが var
パターンで使用されます。
ref ローカル変数
ref
ローカルを宣言するには、変数の型の前に ref
キーワードを追加します。 ref
ローカルは、他のストレージを "参照する" 変数です。 GetContactInformation
メソッドが ref 戻り値として宣言されているとします。
public ref Person GetContactInformation(string fname, string lname)
次の 2 つの割り当てを比較してみましょう。
Person p = contacts.GetContactInformation("Brandie", "Best");
ref Person p2 = ref contacts.GetContactInformation("Brandie", "Best");
変数 p
は、GetContactInformation
からの戻り値の "コピー" を保持します。 これは、GetContactInformation
からの ref
戻り値とは別のストレージの場所です。 p
のいずれかのプロパティを変更すると、Person
のコピーが変更されます。
変数 p2
は、GetContactInformation
からの ref
戻り値のストレージの場所を "参照" します。 これは、GetContactInformation
からの ref
戻り値と同じストレージです。 p2
のいずれかのプロパティを変更すると、Person
のその 1 つのインスタンスが変更されます。
同じ方法で、参照渡しの値にアクセスできます。 場合によっては、参照渡しの値へのアクセスによって負荷がかかる可能性があるコピー操作が回避され、パフォーマンスが向上します。 たとえば、次のステートメントは、値の参照に使用される ref ローカル値をどのように定義できるかを示しています。
ref VeryLargeStruct reflocal = ref veryLargeStruct;
ref
キーワードは、ローカル変数宣言の前 "および" 2 番目の例の値の前で使用します。 両方の例の、変数宣言と代入の両方の ref
キーワードを含めないと、コンパイラ エラー CS8172 "値を使用して参照渡し変数を初期化することはできません" が生成されます。
ref VeryLargeStruct reflocal = ref veryLargeStruct; // initialization
refLocal = ref anotherVeryLargeStruct; // reassigned, refLocal refers to different storage.
ref ローカル変数は、引き続き宣言時に初期化する必要があります。
次の例では、整数値の配列を格納する NumberStore
クラスを定義しています。 FindNumber
メソッドは、引数として渡された数値に等しいかそれより大きい最初の数値を参照渡しで返します。 引数に等しいかそれより大きい数値がない場合、メソッドはインデックス 0 の数値を返します。
using System;
class NumberStore
{
int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };
public ref int FindNumber(int target)
{
for (int ctr = 0; ctr < numbers.Length; ctr++)
{
if (numbers[ctr] >= target)
return ref numbers[ctr];
}
return ref numbers[0];
}
public override string ToString() => string.Join(" ", numbers);
}
次の例では、NumberStore.FindNumber
メソッドを呼び出して、16 に等しいかそれより大きい最初の値を取得します。 呼び出し元は、メソッドによって返された値を 2 倍にします。 次の例の出力では、NumberStore
インスタンスの配列要素の値に変更が反映されたことが示されています。
var store = new NumberStore();
Console.WriteLine($"Original sequence: {store.ToString()}");
int number = 16;
ref var value = ref store.FindNumber(number);
value *= 2;
Console.WriteLine($"New sequence: {store.ToString()}");
// The example displays the following output:
// Original sequence: 1 3 7 15 31 63 127 255 511 1023
// New sequence: 1 3 7 15 62 63 127 255 511 1023
参照戻り値がサポートされていない場合、このような操作は、配列要素のインデックスと値を返すことによって実行されます。 呼び出し元はこのインデックスを使用して、別のメソッド呼び出しで値を変更できます。 一方、インデックスを変更して配列の他の値にアクセスし、変更することも可能です。
次の例では、ref ローカル変数の再割り当てを使用するように FindNumber
メソッドを書き直す方法を示します。
using System;
class NumberStore
{
int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };
public ref int FindNumber(int target)
{
ref int returnVal = ref numbers[0];
var ctr = numbers.Length - 1;
while ((ctr >= 0) && (numbers[ctr] >= target))
{
returnVal = ref numbers[ctr];
ctr--;
}
return ref returnVal;
}
public override string ToString() => string.Join(" ", numbers);
}
この 2 番目のバージョンは、検索対象の数字が配列の末尾に近いシナリオで、シーケンスが長い場合に、より効率的です。配列は末尾から先頭に向かって反復処理されるので、検査される項目が少なくて済むためです。
コンパイラは、ref
変数 (ref struct
型の ref
ローカル、ref
パラメーター、ref
フィールド) にスコープの規則を適用します。 この規則により、参照が参照先のオブジェクトよりも長く存在しないようにします。 メソッド パラメーターに関する記事のスコープの規則に関するセクションを参照してください。
ref と readonly
readonly
修飾子は、ref
ローカル変数と ref
フィールドに適用できます。 readonly
修飾子は、式の右側に影響します。 次の宣言例を参照してください。
ref readonly int aConstant; // aConstant can't be value-reassigned.
readonly ref int Storage; // Storage can't be ref-reassigned.
readonly ref readonly int CantChange; // CantChange can't be value-reassigned or ref-reassigned.
- "値の再割り当て" は、変数の値が再割り当てされたことを意味します。
- "ref 代入" は、変数が別のオブジェクトを参照することを意味します。
readonly ref
および readonly ref readonly
宣言は、ref struct
の ref
フィールドでのみ有効です。
scoped ref
コンテキスト キーワード scoped
は、値の有効期間を制限します。 scoped
修飾子は、ref-safe-to-escape または safe-to-escape 有効期間をそれぞれ現在のメソッドに制限します。 実際には、scoped
修飾子を追加すると、コードによって変数の有効期間が延長されないというアサートが行われます。
パラメーターまたはローカル変数に scoped
を適用できます。 scoped
修飾子は、型が ref struct
の場合にパラメーターとローカルに適用されます。 それ以外の場合、scoped
修飾子は、ref 型のローカル変数にのみ適用できます。 これには、ref
修飾子で宣言されたローカル変数と、in
、ref
、または out
修飾子で宣言されたパラメーターが含まれます。
scoped
修飾子は、型が ref struct
の場合は、struct
、out
パラメーター、および ref
パラメーターで宣言されたメソッドの this
に暗黙的に追加されます。
C# 言語仕様
詳細については、「C# 言語仕様」の「宣言ステートメント」セクションを参照してください。
scoped
修飾子の詳細については、「低レベル構造体の機能強化」の提案メモを参照してください。