連載:C# 3.0入門

第3回 varによる変数宣言とコレクション初期化子

株式会社ピーデー 川俣 晶
2008/06/13
Page1 Page2 Page3 Page4

暗黙的に型指定されるローカル変数

 C# 3.0では、明示的に型を指定することなく、ローカル変数を宣言することができる。

 例えば、

int i = 123;

は、

var i = 123;

と書くことができる。

 「var」というキーワードは「暗黙的に型指定されるローカル変数」を宣言するためのキーワードなので、int型以外の変数を宣言する場合にも使用できる。例えば、

string s = "Welcome to C# 3.0";

は、

var s = "Welcome to C# 3.0";

と書いてもよい。

 もちろん、自分で定義したクラス「MyClass」があるとき、

MyClass instance = new MyClass();

と書く代わりに、

var instance = new MyClass();

と書くこともできる。

 このような機能に対しては、過剰な肯定と、過剰な否定という2つの典型的なリアクションがしばしば見られるように思う。

 過剰な肯定とは、LL(Lightweight Language、軽量プログラミング言語)の信奉者などに見られるものである。彼らは、型をソース・コード上に明示せず、実行時に動的に型を扱うことがよいと主張することが多い。上記のような構文は、型の解決がコンパイル時から実行時に遅延されたかのように見えるために、C#が静的に型付けされた言語から、動的に型付けされた言語に変わったかのように見えることがある。

 一方、過剰な否定とは、Visual Basic(以下VB)におけるVariant型の悪夢再来と受け止めるタイプである。この件は後で詳しく説明する。

 この2つはいずれも誤解でしかないので、誤って思い込むと、C# 3.0をうまく扱えないワナにはまり込むリスクがある。そこでこの件について、詳しく説明してみよう。

Variant型の悪夢

 C# 3.0の「暗黙的に型指定されるローカル変数」を分かりやすく説明するために、それとは明らかに違うVBのVariant型を紹介し、相違点を示すことにする。違うものとの対比を行う方が、特徴が分かりやすく浮かんでくるからである。

 さて、VBのVariant型とは、VB 2.0から6.0までに存在するデータ型で、どのような型の値でも格納できる「魔法の箱」として機能する。変数宣言時に型を省略した場合はこの型になるので、型を一切明示せずにソース・コードを記述することも不可能ではない(明示的な型変換を行うコードを含む可能性はあるが)。Variant型を、(6.0までの)VBらしさを象徴する特徴的な機能と思う技術者も多いのではないかと思う。特に、日本ではVB 1.0日本語版がリリースされておらず、Variant型導入前のVBを知らない技術者が大多数と思われるため、その傾向は顕著だろう。

 しかしながら、筆者はVB 1.0時代からVBを使い込んでいたので、違う印象を持っている。VBに詳しければ詳しいほど意外に思われる可能性があるが、実はVB 1.0とは厳格に型付けされた言語であった。何でも入る「魔法の箱」は存在せず、変数を宣言する際に型を省略すると整数(Integer)型と見なされ、整数は代入できても文字列は代入できない変数だったのである。

 型はソース・コード上で明示する方がよい、と思っていたので、筆者はこのようなVBの仕様を素直に受け止め、変更される可能性などまったく考えていなかった。しかし、VB 2.0がやって来たとき、あらゆる機能が好ましい意味で改良されていたにもかかわらず、Variant型が導入されて型を明示しないプログラミングが可能になったことに驚いた。その当時、これは悪夢そのものであり、Variant型は使うべきではない、と筆者は考えた。VB 2.0唯一の決定的な弱点と思ったのである。

 ちなみに、何でも入る魔法の型といえば、C#ではobject型がそれに当たる。しかし、object型とVariant型は同じではない。object型は参照型であり、値型の値を扱うにはボックス化という手順が必要になる。また、参照型である以上、メモリの確保と破棄というオーバーヘッドが必ずついて回る。しかし、Variant型は整数のような値であっても、オブジェクトへの参照であっても、それをそのまま格納できる。ボックス化やメモリの確保、解放のオーバーヘッドもない。まさに万能の魔法の箱である。

 では、Variant型の何が問題なのだろうか。Variant型の悪夢の典型的な例を1つ挙げよう。コメントを多く補ったので、VBの文法を知らなくても読めるだろう。

Private Sub Form_Load()

  Dim a, b      ' 変数aとbを宣言(Variant型)

  a = 1              ' 整数の1を代入
  b = "2"            ' 文字列の「2」を代入
  Debug.Print a + b  ' aとbを加算して出力

  a = "1"            ' 文字列の「1」を代入
  b = 2              ' 整数の2を代入
  Debug.Print a + b  ' aとbを加算して出力

  a = "1"            ' 文字列の「1」を代入
  b = "2"            ' 文字列の「2」を代入
  Debug.Print a + b  ' aとbを加算して出力

End Sub
リスト1 Variant型の悪夢の例 (VB 6.0で記述し、動作確認した)

 3
 3
12
リスト1の実行結果

 最初の2つの加算では、整数と、文字列を整数化した値を加算して結果としている。そこで、同様の結果を期待して文字列と文字列を加算させると期待は裏切られて、「整数の足し算」ではなく「文字列連結」が行われてしまう。このような結果になる理由はVariant型ではなく+演算子が持つ機能性にある。ここで、Variant型が悪夢を持ち込むのは、原因と結果の因果関係を切り離す効能を持つためである。

 もし、"1" + "2"という式を直接書けば、結果が3ではなく"12"になることは誰でも容易に予測できるだろう。しかし、Variant型変数が介在することで、原因と結果の因果関係が見えにくくなり、すぐ原因が推定できない深刻なバグを作り込んでしまうリスクを持つ。このような問題を解決する最も簡単なやり方は、型を明示することである。

 変数の宣言を以下のように書き換え、整数を示す型名であるIntegerを明示的に指定する。

Dim a As Integer, b As Integer

 これで実行結果は以下のようになり、意図どおりとなる。

 3
 3
 3
リスト1で変数a、bをInteger型にした場合の実行結果

 さて、最初にC# 3.0の「暗黙的に型指定されるローカル変数」を見たときに感じたのは、上記のような「Variant型の悪夢」の再来であった。しかし、それはまったくの誤解であった。果たして、どのような誤解だったのだろうか?

暗黙的に型を明示する

 Variant型の悪夢の例を、varキーワードを使ってできるだけ忠実にC# 3.0に書き換えてみよう。すると、すぐに重大な問題に直面することになる。

 VB 6.0の「Dim a, b」に相当する、以下の変数宣言がエラーになってコンパイルできないのである。

var a, b;
// エラー  1  暗黙的に型指定されたローカル変数を初期化しなければなりません
// エラー  2  暗黙的に型指定されたローカル変数には複数の宣言子を指定できません

 つまり1つの宣言では1つの変数しか宣言できず、かつ、初期化されねばならないわけである。

 この構文上の制約を受け入れたうえで、C#に書き直してみよう。しかし、このコードはコンパイルできない。

using System;

class Program
{
  static void Main(string[] args)
  {
    var a = 1;
    var b = "2";
    Console.WriteLine(a + b);

    a = "1";
    // エラー  1  型 'string' を型 'int' に暗黙的に変換できません。

    b = 2;
    // エラー  2  型 'int' を型 'string' に暗黙的に変換できません。

    Console.WriteLine(a + b);

    a = "1";
    // エラー  3  型 'string' を型 'int' に暗黙的に変換できません。

    b = "2";

    Console.WriteLine(a + b);
  }
}
リスト2 コンパイルできない「Variant型の悪夢・C#版」

 見てのとおり、Variant型の変数aには整数を入れた後で文字列を入れることが可能だったのに対して、「暗黙的に型指定されるローカル変数」の変数aは、整数で初期化した後で文字列を代入することはできない。それは、実行時ではなくコンパイル時にエラーとなってはじかれてしまうのである。

 つまり、「暗黙的に型指定されるローカル変数」がVariant型と似ているように見えるとしても、それは単なる錯覚にすぎず、両者はまったく異なる機能性を持っているのである。従って、上記のような「Variant型の悪夢」はC# 3.0においては発生しない。

 まとめると以下のようになる。

  • 「暗黙的に型指定されるローカル変数」は、変数の宣言に伴い初期化のための式によって型が決定され、その型は厳格に貫徹される(「var a = 1;」と「int a = 1;」は完全に等価)
  • 型はコンパイル時に確定し、コンパイル時にチェックされる。LL信奉者に期待する者が多い「実行時まで型の解釈を遅延する」機能性とはまったく違う。逆に、できるだけコンパイル時に型チェックをさせたい者たちが期待する機能性に合致する
 

 INDEX
  C# 3.0入門
  第3回 varによる変数宣言とコレクション初期化子
  1.暗黙的に型指定されるローカル変数/Variant型の悪夢/暗黙的に型を明示する
    2.なぜvarを使うのか?/varが使用できない場面
    3.varが使用できる場面/varとローカル配列/var配列と型の推測/var配列とnull
    4.コレクション初期化子/Dictionaryクラスとコレクション初期化子/コラム
 
インデックス・ページヘ  「C# 3.0入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間