未割り当てな変数とdefault演算子とdefaultリテラル
《 初回公開:2020/07/12 , 最終更新:未 》
visual studio2019 C#8.0で動作確認。
【 目次 】
未割り当てな変数値
未割り当ての変数に対する扱いは、言語によって多少の違いがある。
ここで言う未割り当ての変数というのは、変数を定義,宣言しただけで値を代入していない変数という事。
ローカル変数
C#の場合、ローカル変数では未割り当ての変数を使用しようとするとエラーになってしまう。
private static void Test1() { int i; // i = 0; // 値を代入すればエラーにならない Console.WriteLine($"i = {i}"); }
エラー CS0165 未割り当てのローカル変数 'i' が使用されました。
未割り当ての変数には0とかnullとかが、初期値として代入されている訳ではないという事。
何も値が入っていないので使えない。
VB.NETの場合、振る舞いが異なって未割り当ての変数にはデフォルトの値が代入される。
Sub Main(args As String()) Dim i As Integer i = i + 1 Console.WriteLine($"i = {i}") Dim s As String If s = String.Empty Then Console.WriteLine("s is Empty") End If End Sub
実行結果
i = 1 s is Empty
その他の言語では
またC++の場合は、その時のメモリーの状態により値が不定になる。
そして、これがもとでバグの原因となる事も多々あり。
JavaScriptの場合は、未割り当ての変数にはundefined
という値があらかじめ定義されていて(これはnullとは異なる)。
言語によってこのような違いがあるのは
C言語の場合は初期化処理を省く事によりパフォーマンスを上げる
VB.NETの場合はプログラム初心者に対しても簡単で使いやすくするため、そしてVB6からの互換性を考えて
C#の場合はパフォーマンスを上げつつもバグの発生を抑えるため
と考えられる。
フィールド変数
ただこれはローカル変数の場合であってクラスのフィールド変数では、宣言しただけで値を代入していない変数には変数の型に応じて初期値として、defaultの値が代入されているのでエラーにならない。
class MyClass { // 未割り当てのフィールド変数 int i; // 未割り当てのフィールド変数を表示 public void ShowMe() => Console.WriteLine($"i = {i}"); } private static void Test2() { new MyClass().ShowMe(); }
実行結果
i = 0
ローカル変数とかフィールド変数とかグローバル変数とか
ここでローカル変数とかフィールド変数とか出てきたが、
ローカル変数とはメソッド内で宣言された変数の事で、
フィールド変数とはクラスのメンバーとして宣言された変数の事。
また、C#ではローカル変数があるからと言って、対義するグローバル変数というのは存在しない。
しかし、staticなフィールド変数(クラス変数)をグローバル変数と呼んだりする人もいるみたいで
またstaticでないフィールド変数(インスタンス変数)もクラス内のインスタンスメソッド内からみればグローバル変数のように見える。
配列の場合
配列の場合も同様に、ローカル変数を宣言しただけでは未割り当ての変数は初期化されない。
int[] ary_i; Console.WriteLine(ary_i); // エラー CS0165 未割り当てのローカル変数 'ary_i' が使用されました。
そしてフィールド変数ではnullに初期化される。
では、配列の要素についてはどうかというと
int[] ary_i = new int[3]; Console.WriteLine(string.Join(", ", ary_i));
実行結果
0, 0, 0
配列に要素数を割り当てると配列の各要素には初期値が与えられる。(int型の場合には0が)
既定値
未割り当ての変数は、フィールド変数ではdefaultの値(既定値)が代入されると述べた。
では、この既定値の値とはどのような値なのだろうか。
次のカテゴリの変数は、自動的に既定値に初期化されます。
- 静的変数
- クラスインスタンスのインスタンス変数。
- 配列要素。
変数の既定値は、変数の型によって異なり、次のように決定されます。
- Value_typeの変数の既定値は、 value_typeの既定のコンストラクター (既定のコンストラクター) によって計算された値と同じです。
- Reference_typeの変数の場合、既定値はnullです。
既定のコンストラクタというのは引数無しのパブリックなコンストラクタであって。
つまり値型では、既定のコンストラクタによってゼロのビットパターンによって生成される値に初期化される。
この事は、参照型ではnullが代入される事を意味する。
既定値式
未割り当ての値に対して型の既定値を割り当てたい場合がある。
その場合にdefaultリテラルやdefault演算子が使われる。
default演算子
defaultというキーワードの後に、カッコ内に型名を指定する事でその型の既定値を生成できる。
int i = default(int); Console.WriteLine($"i = {i}");
実行結果
i = 0
default演算子はカッコ内に型を指定するので変数の型は明確。
従って、varキーワード(暗黙的に型指定されるローカル変数)を使って宣言する事ができる。
var i = default(int);
暗黙的に型指定されるローカル変数というのは。
defaultリテラル
逆にvarを使わずに型名を指定して変数を宣言する場合は、カッコを使った型の指定を省略できて、これをdefaultリテラルと呼ぶ。
int i = default;
既定のコンストラクタとdefault
既定値式 - default演算子とdefaultリテラルでは、既定のコンストラクタつまり引数無しのコンストラクタが呼ばれる。
この事をクラスを定義して確認してみる。
class MyClass2 { int i; public MyClass2() { Console.WriteLine($"i = {i}"); i = 99; } public void ShowMe() => Console.WriteLine($"i = {i}"); } private static void Test7() { new MyClass2().ShowMe(); }
実行結果
i = 0 i = 99
既定のコンストラクタによってi = 0
が表示されて、その後ShowMeメソッドによってi = 99
が表示されるのがわかる。
ただ、変数に既定値式defaultが代入された時に必ず既定のコンストラクタが呼び出されるわけでは無くて、
変数に既定値式defaultが代入されただけでは既定のコンストラクタは呼び出されない
var myClass2 = default(MyClass2);
その後、変数に対する何らかのアクセスがあった時に初めて、既定のコンストラクタが呼び出されるようだ。
デフォルトの引数やジェネリック型default(T)
にも既定値式を使う事ができて。
private static void Test8(MyClass2 myClass2 = default) { // デフォルトの引数に既定値式を指定 }