|
|
連載:C# 3.0入門
第3回 varによる変数宣言とコレクション初期化子
株式会社ピーデー 川俣 晶
2008/06/13 |
|
|
varが使用できる場面
特にvarが使用できる場面も2つ紹介しておこう。using文やforeach文でもvarは活用できるのである。
■usingステートメントで使用するvar
usingステートメントでもvarは使用できる。そして、このケースではほかのケース以上にメリットを甘受できる印象がある。usingステートメントは、確実なファイルのクローズを実現するためにファイルの入出力とともに使うことも多いが、このとき、式が長くなる頻度が割と高い。なぜかといえば、以下のような理由があるためである。
- そのソース・ファイル中でファイルを扱うコードが少ないときは、「using System.IO;」を入れないで、クラス名をフルネームで書いて使用したいことがある(例えば、Fileクラスを使用するのに「System.IO.File」と書けば、「using System.IO;」は書かなくてよい)
- そもそもVisual Studioのデフォルトでは「using System.IO;」は記述されていないので、書き足さないで簡単にソースが書けるなら、そうしたい
- 式にはファイル名などの引数が多く入り、引数も長くなりがち
以下は、実際に名前空間込みで記述して長くなった例である。
using System;
class Program
{
static void Main(string[] args)
{
using (System.IO.StreamWriter writer
= System.IO.File.CreateText("log.txt"))
{
writer.WriteLine("application log……");
}
}
}
|
|
リスト7 名前空間込みで長くなるクラス名 |
このusingステートメントの行は、以下のようにvarを使って書き直すと、大幅に読みやすくなる。
using (var writer = System.IO.File.CreateText("log.txt"))
|
|
■foreachステートメントで使用するvar
C#のforeachステートメントは、列挙するアイテムの型を明示することができる。この特徴はC# 1.0時代には非常に便利であった。なぜなら、ジェネリック導入前の.NETのコレクションは型を明示しない何でも入るスタイルだったからである。
以下のようなコードは、キャストを書くことなく、汎用コレクションから整数を取り出しつつ列挙することを可能とした。キャストなしで「item * 2」という乗算が可能となるのは、foreachステートメントでint型を明示して、コレクションのアイテムを受け止めているためである。
using System;
using System.Collections;
class Program
{
static void Main(string[] args)
{
ArrayList items = new ArrayList();
items.Add(123);
foreach (int item in items)
{
Console.WriteLine(item * 2);
}
}
}
|
|
リスト8 型の明示が意味を持つforeachステートメント |
しかし、ジェネリック導入後のC#にとって、foreach文での型の明示はあまり意味のある機能性とはいえない。コレクションを列挙する場合、列挙されるアイテムの型はコレクションの型に決まっているためである(あえて別の型で受けるという選択肢がないわけではないが)。
従って、C# 3.0ではforeachステートメントにvarを用いて、列挙されるアイテムの型をソース・コード上で明示しないことも有効だろう。
using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
var items = new List<int>();
items.Add(123);
foreach (var item in items)
{
Console.WriteLine(item * 2);
}
}
}
|
|
リスト9 列挙されるアイテムの型をソース・コード上で明示しない例 |
ちなみに、単に型を明示しないで列挙するだけなら、ラムダ式を使う方法もある。
using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
var items = new List<int>();
items.Add(123);
items.ForEach((item) =>
{
Console.WriteLine(item * 2);
});
}
}
|
|
リスト10 ラムダ式を使った型を明示しない列挙 |
暗黙的に型指定されるローカル配列
ローカル変数として宣言される配列も、ローカル変数の一種である以上、varを用いて宣言することができる。
例えば、
int [] array = new int[10];
|
|
は、
と書いてもよい。しかし、
int[] array = { 1, 2, 3 };
|
|
を、以下のように書き換えることはできない。
var array = { 1, 2, 3 };
// エラー 1 暗黙的に型指定されたローカル変数を配列初期化子で初期化できません
|
|
しかし、C# 3.0で拡張された「暗黙に型付けされた配列」(new [] {……})という機能を用いると、これは以下のように記述することができる。
var array = new [] { 1, 2, 3 };
|
|
この構文を使う場合、「new []」の後に初期化リストを書けばよい。この場合には、配列の要素の型は、初期化に使用される値(この場合では1、2、3)より推測される。
暗黙に型付けされた配列と型の推測
通常の変数と違って、配列の初期化リストは複数の型の値を列挙できるため、解釈が難しい。
まず、継承された3つのクラスのインスタンスを使用した事例から、どのように型が推測されるかを見てみよう。
using System;
class A
{
}
class B : A
{
}
class C : A
{
}
class Program
{
static void Main(string[] args)
{
var case1 = new[] { new A(), new A(), new A() };
var case2 = new[] { new B(), new B(), new B() };
var case3 = new[] { new C(), new C(), new C() };
var case4 = new[] { new A(), new B(), new C() };
Console.WriteLine(case1.GetType().Name); // 出力:A[]
Console.WriteLine(case2.GetType().Name); // 出力:B[]
Console.WriteLine(case3.GetType().Name); // 出力:C[]
Console.WriteLine(case4.GetType().Name); // 出力:A[]
}
}
|
|
リスト11 推測された配列の型を確認する |
つまり、変数case1〜case3が示すように、型が統一されていればその型が採用される。しかし、変数case4のように、配列内に複数の型が混在する場合、すべての値が変換可能な型として、Aが選択され、A[]という型になっている。
しかし、推測は万能ではない。以下のコードはコンパイル・エラーになる。
var case5 = new[] { new B(), new B(), new C() };
// エラー 1 暗黙的に型指定された配列の最適な型が見つかりませんでした
|
|
このケースでは、変数の型をA[]とすれば記述可能である。しかし、この初期化リストに含まれる型の候補はBとCであり、Aは含まれない。それ故に、A[]という推測はできず、コンパイルはエラーになる。
逆にいえば、この配列は以下のように型を明示すればコンパイルできる。
A[] case5 = { new B(), new B(), new C() }; // OK
|
|
なお、数値に関してもこのような推測が行われる。例えば、整数(int)と実数(double)が混在した初期化リストがあるとき、これはdouble[]と推測される。int型は暗黙的にdouble型に変換可能だからである。
var a = new[] { 1, 1.1, 1.2 }; // aはdouble[]型と推測される
|
|
暗黙に型付けされた配列とnull
「暗黙に型付けされた配列」でも、「暗黙的に型指定されるローカル変数」と同様に、やはりnullは特殊な扱いを受ける。しかし、nullが常に受け付けられないわけではない。
例えば、以下のケースは有効である。文字列である"A"や"B"によって型を推測できるので、nullが含まれていても推測できる。
var case1 = new[] { "A", "B", null }; // case1はstring型
|
|
しかし、以下のケースでは型を推測する手掛かりがないので、コンパイル・エラーとなる。
var case2 = new[] { null, null, null };
//エラー 1 暗黙的に型指定された配列の最適な型が見つかりませんでした
|
|
とはいえ、型のヒントさえあればよいので、以下のようにnull値にキャストを付けて型を明示してやれば、コンパイル可能となる。
var case3 = new[] { (string)null, null, null }; // case3はstring型
|
|
しかし、以下のように型を明示して書く方が短く書けるので、あまり役立つテクニックでもないだろう。
string [] case4 = { null, null, null };
|
|