宣言ステートメント
"宣言ステートメント" では新しい変数を宣言し、必要に応じて変数を初期化します。 すべての変数には宣言された型があります。 型の詳細については、.NET 型システムに関する記事を参照してください。 通常、宣言には型と変数名を含めます。 また、初期化を含めることもできます。その場合、=
演算子の後に式を続けます。 型は var
に置き換えることができます。 宣言または式に ref
修飾子を含めて、新しい変数が既存の記憶域の場所を参照していることを宣言できます。
暗黙的に型指定されたローカル変数
メソッド スコープで宣言された変数には暗黙的な "型" var
を指定できます。 暗黙的に型指定されたローカル変数では、型を自分で宣言した場合と同様に厳密に型指定されますが、コンパイラが型を決定します。 次の 2 つの a
と b
の宣言は機能的に等しくなります。
var a = 10; // Implicitly typed.
int b = 10; // Explicitly typed.
重要
var
を null 許容参照型を有効にして使用すると、式の型で null 値が許容されない場合でも、常に null 値を許容する参照型を表します。 コンパイラの null 状態分析により、潜在的な null
値の逆参照から保護されます。 null である可能性がある式に変数が割り当てられない場合、コンパイラから警告は生成されません。 null である可能性がある式に変数が割り当てる場合、警告が生成されないように、逆参照の前に null でないことをテストする必要があります。
var
キーワードは、コンストラクターの呼び出し式と共に使用するのが一般的です。 var
を使用すると、次の例に示すように、変数宣言およびオブジェクトのインスタンス化において型名を繰り返す必要がなくなります。
var xs = new List<int>();
C# 9.0 以降では、代替としてターゲット型の new
式を使用できます。
List<int> xs = new();
List<int>? ys = new();
パターン マッチングでは、var
キーワードが var
パターンで使用されます。
次の例は、2 つのクエリ式を示しています。 最初の式では、var
の使用が許可されますが、必須ではありません。クエリ結果の型を IEnumerable<string>
として明示的に指定できるためです。 一方、2 つ目の式の var
では、匿名型のコレクションとしての結果が許され、その型の名前にはコンパイラ自体以外はアクセスできません。 var
を使うと、結果のために新しいクラスを作成する必要がなくなります。 例 #2 では、foreach
繰り返し変数 item
も暗黙的に型指定する必要があります。
// Example #1: var is optional when
// the select clause specifies a string
string[] words = { "apple", "strawberry", "grape", "peach", "banana" };
var wordQuery = from word in words
where word[0] == 'g'
select word;
// Because each element in the sequence is a string,
// not an anonymous type, var is optional here also.
foreach (string s in wordQuery)
{
Console.WriteLine(s);
}
// Example #2: var is required because
// the select clause specifies an anonymous type
var custQuery = from cust in customers
where cust.City == "Phoenix"
select new { cust.Name, cust.Phone };
// var must be used because each item
// in the sequence is an anonymous type
foreach (var item in custQuery)
{
Console.WriteLine("Name={0}, Phone={1}", item.Name, item.Phone);
}
ref ローカル変数
ref
ローカルを宣言するには、変数の型の前に ref
キーワードを追加します。 GetContactInformation
メソッドが ref 戻り値として宣言されているとします。
public ref Person GetContactInformation(string fname, string lname)
値渡し代入によって、変数の値が読み取られ、それが新しい変数に渡されます。
Person p = contacts.GetContactInformation("Brandie", "Best");
先の代入では、ローカル変数として p
が宣言されています。 その初期値は、GetContactInformation
によって返された値の読み取りからコピーされます。 今後、p
に値が代入されることで、GetContactInformation
によって返された変数の値が変わることはありません。 変数 p
は、返された変数のエイリアスではなくなります。
ref 変数を宣言し、元の値にエイリアスをコピーします。 次の代入では、p
は、GetContactInformation
から返された変数のエイリアスになります。
ref Person p = ref contacts.GetContactInformation("Brandie", "Best");
この後 p
を使用することは、GetContactInformation
によって返された変数を使用することと同じです。p
はその変数のエイリアスであるためです。 p
を変更すると、GetContactInformation
から返される変数も変更されます。
同じ方法で、参照渡しの値にアクセスできます。 場合によっては、参照渡しの値へのアクセスによって負荷がかかる可能性があるコピー操作が回避され、パフォーマンスが向上します。 たとえば、次のステートメントは、値の参照に使用される 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
に暗黙的に追加されます。