変数は、型が固定された、名前の付いた格納場所です。C#では型と名前で宣言し、必要なら同じ文で値を与えます。その後に型が変わることはありません。int の変数は、存在する限り整数を保持します。
出力:
Priya bought 5 items for 20.25
10
間違った型の値を代入するとコンパイルエラーになります。quantity = "five"; はCS0029(cannot implicitly convert type 'string' to 'int')で失敗します。型の間の変換は明示的に行います。型変換を参照してください。
var:コンパイラに型を推論させる
var は、初期化子から型が推論されるローカル変数を宣言します。動的型付けではありません。型は、明示的に書いた場合とまったく同じく、コンパイル時に固定されます。
出力:
String
Int32
Decimal
Double
List`1
var には3つのルールがあります。ローカル変数にしか使えません(フィールドや引数には使えません)。変数は宣言で初期化しなければなりません。var x; はエラーCS0818「Implicitly-typed variables must be initialized」です。そして初期化子は型を持たなければなりません。null だけでは型がないので、var nothing = null; はコンパイルできません。
いつ使うかはチームのスタイルの問題です。一般的な指針は、右辺から型が明らかなら var を使い、そうでなければ型を書く、というものです。
var orders = new List<Order>(); // obvious: var is fine
var reader = new StreamReader(path); // obvious
decimal discount = CalculateDiscount(); // not obvious: name the type
int count = items.Count(); // makes the intent clear
微妙な落とし穴があります。var total = 0; では total が int になるので、後で 2.5 を足すと失敗し、var price = 10; は decimal ではなく int です。リテラルの型が望むものでないなら、型を書くか接尾辞(10m、0.0)を付けます。
スコープ
ローカル変数は、宣言から、それが宣言されたブロック { } の終わりまで存在します。ループのヘッダーや if の本体で宣言した変数は、その後では見えません。
出力:
high
240
CやC++と違い、C#では、内側のブロックで外側のブロックと同じ名前のローカル変数を宣言することはできません(エラーCS0136)。1つのメソッドの中では、名前は1つのものを意味しなければなりません。兄弟関係にある2つのブロックでは名前を再利用でき、2つの別々の for ループがそれぞれ i を宣言するのがその例です。
確実な代入
ローカル変数には既定値がありません。コンパイラはメソッドのすべての経路を追跡し、変数が代入される前に読まれるかもしれない場合はコンパイルを拒否します。
int discount;
if (isMember)
{
discount = 10;
}
Console.WriteLine(discount);
// error CS0165: Use of unassigned local variable 'discount'
宣言時に初期化する(int discount = 0;)か、すべての分岐で代入する(else を追加する)ことで直します。フィールドと配列の要素は異なり、その型の既定値(0、false、null)に自動で設定されます。
const:コンパイル時定数
const はコンパイル時に固定される値です。定数式(リテラル、別のconst、それらの演算)で初期化しなければならず、数値、char、bool、string、enum、null にしかできません。
出力:
Price in EUR
120.00 EUR
350
constのフィールドは暗黙的に static なので、型名を通してアクセスし(Pricing.VatRate)、static const と書くとエラーになります。コンパイラは使われるすべての場所に値をコピーします。だからconstは実行時にコストがかからず、同時に、ライブラリの公開のconstは(1週間の日数のように)決して変わらない値であるべきなのです。
readonlyとstatic readonly
readonly のフィールドに代入できるのは、宣言の中か、そのクラスのコンストラクターの中だけです。構築の後は変更できません。const と違い、値は実行時に計算され、任意の型にでき、オブジェクトごとに異なる値にできます。
出力:
ACC-17 opened 2024-05-02
Launch: 2020-01-15
basic, pro
最後の行は、よくある誤解を示しています。readonly が保護するのはフィールドであって、それが指すオブジェクトではありません。Plans は常に同じ配列を参照しますが、配列の要素は変更できます。本当に不変のコレクションには、ReadOnlyCollection<T> や ImmutableArray<T> を使います。
3つのまとめ:
const | readonly | static readonly | |
|---|---|---|---|
| 値が決まるとき | コンパイル時 | 実行時 | 実行時 |
| 使える型 | 数値、char、bool、string、enum、null | 任意 | 任意 |
| 設定する場所 | 宣言のみ | 宣言またはコンストラクター | 宣言または静的コンストラクター |
| オブジェクトごとか共有か | 共有(暗黙的に静的) | オブジェクトごと | 共有 |
| ローカル変数 | 可(const int x = 1;) | 不可 | 不可 |
命名規則
C#の名前は大文字と小文字を区別し(total と Total は別の変数)、英字かアンダースコアで始めなければなりません。ほぼすべてのC#のコードが従っているMicrosoftの規則は次のとおりです。
- ローカル変数と引数:camelCase(
orderCount、unitPrice)。 - 定数、プロパティ、メソッド、クラス、公開フィールド:PascalCase(
MaxItems、VatRate)。 - privateなフィールド:先頭にアンダースコアを付けた _camelCase(
_balance)。.NETのコードで広く使われている規則です。 - ブール値は疑問文のように読める名前にします:
isActive、hasDiscount、canRetry。 - 省略形やハンガリアン記法の接頭辞(
strName、iCount)は避けます。
C#のキーワードは、@ を前に付けない限り名前にできません。var @class = "5B"; は正しい構文で、自動生成されるコードやJSONのフィールド名でときどき役に立ちます。
値の入れ替えと複数の代入
C# 7以降、タプルを使うと1つの文で複数の変数に代入でき、1行で値を入れ替えられます。
出力:
20 10
Lisbon: 545,000
2つ目の形は分解です。タプルから、city を string として、population を int として宣言します。詳しくはタプルを参照してください。
よくある質問
C#のvarとは何ですか?
var は、初期化に使う値からローカル変数の型を推論するようコンパイラに伝えます。var count = 5; は int を宣言します。変数は静的型付けのままで、後から文字列を保持することはできません。var が使えるのは、同じ文で初期化されるローカル変数だけです。
C#のconstとreadonlyの違いは何ですか?
const はコンパイル時定数です。値はコンパイル時にわかっていなければならず(数値、文字列、bool、null)、暗黙的に静的で、使われるすべての場所に値がコピーされます。readonly のフィールドは、宣言かコンストラクターで実行時に1回だけ設定され、任意の型を保持でき、オブジェクトごとに異なる値にできます。
constではなくstatic readonlyを使うべきなのはどんなときですか?
DateTime、配列、オブジェクトなど、コンパイル時定数にできない共有の値や、ライブラリのバージョン間で変わるかもしれない公開の値には static readonly を使います。const の値は呼び出し側のアセンブリにコピーされるので、公開のconstを変更すると、それを使うすべてのアセンブリを再コンパイルする必要があります。
C#の「use of unassigned local variable」とはどういう意味ですか?
エラーCS0165です。コンパイラが、値が何も代入される前にローカル変数が読まれる経路を見つけました。C#のローカル変数には既定値がありません。宣言時に変数を初期化するか、if/switch のすべての分岐で、使う前に代入されるようにします。
C#ではvarと明示的な型のどちらを使うべきですか?
どちらでもかまわず、チームがスタイルを選びます。よくあるルールは、右辺から型が明らかなら var を使い(var list = new List<string>();)、そうでなければ型を書く(decimal total = GetTotal();)というものです。.NETランタイムのコードベースは、おおよそこのルールに従っています。