Visual Studio 2005を1ヶ月使ってみての使用感

Visual Studio 2005を土日の趣味プログラミングの時間に使い始めて、そろそろ1ヶ月がたとうとしている。これまで使ってみての使用感を5段階で評価するとしたら、4ぐらいだろう。気に入った点を挙げると、

  • Refactor機能(特に名前の変更)
    • これのおかげで置換の使用回数が減った(よく考えてから名前をつけろってのはなしでorz)
  • デバッグ時に変数へカーソルを合わせた時のツールヘルプ
    • こいつが凄い便利。クイックビューアーを起動するまでもなく、オブジェクトの階層をたどれる。

この2つの機能はかなり便利。ただ、少し残念なのが後者のツールヘルプは値を見ることができない場合が結構ある。どんな場合に値を見れないのかはしっかり検証してないので不明だが、interfaceを通して参照している時にこの問題が多く発生した気がする。

それと、Visual Studio 2005とは直接関係ないのだが、Genericsはかなり良いと思った。最近私はインタプリタを作っているため、リストやディクショナリといったコンテナの使用頻度が非常に高い。今までだと、ArrayListなどの基本コンテナを使うのか独自のコレクションを作るかで悩んでいたところだが、Genericsの環境下ではList<>やDictionary<>といったコンテナを使っておけばまず間違いないように思う。

私は今まで、ArrayListなどのobject型を要素として取る従来のコンテナを使うのに抵抗があった。object型のコンテナを利用すると、型システムによって保障されている安全性が失われるからである。例えば、Employeeのリストから給料の平均値を求めるメソッドがあったとしよう。

public static float CalculateAverageSalary(ArrayList employees)
{
	if ( employees.Count == 0 ) return 0;

	int sumOfSalary = 0;
	foreach(Employee e in employees)
	{
		sumOfSalary += e.salary;	
	}
	return sumOfSalary / employees.Count;
}

CulculateAverageSalary()は例外に対し中立な立場で実装されている。例外に対して中立な立場とは、実行時に発生する例外を検知せずそのままユーザに通知することを指す。この場合、ArrayListの中にEmployee以外のオブジェクトが含まれているとforeachステートメント内でInvalidCastExceptionが発生し、CulculateAverageSalary()の呼び出し元へ通知される。このInvalidCastExceptionこそが型システムの恩恵を受けられていない証拠であり、object型コンテナの問題点である。さて、ここで少し考えてもらいたい。本当にこのコードはInvalidCastExceptionを発生しなければならなかったのだろうか?答えは、否である。そもそも、公開メンバのインターフェイスにobject型のコンテナを利用していることがナンセンスであり実行時例外の原因なのである。この場合、次のようにきちんと型付けされたインターフェイスを定義すればメソッド内でInvalidCastExceptionが発生することはなくなる。

public static float CalculateAverageSalary(Employee[] employees)
{
	//do something ...
}

たしかにこのインターフェイスだと、InvalidCastExceptionは発生しなくなる。しかし、これだとコンテナの型を変換しなければならないという次の問題を引き起こす。普通、固定長である配列を使ってアプリケーションのロジックを記述するという場面はあまりなく、可変長のコンテナを扱う場合がほとんどであろう。Genericsがない環境で可変長コンテナとはArrayListなどのobject型のコンテナがよく使われる。従って、このメソッドの呼び出し元では次のようなコードが実装されている可能性が高い。

ArrayList employeeList = new ArrayList();
//do something ...
Employee employeeArray =
       employeeList.ToArray(typeof(Employee)) as Employee;
float average = CalculateAverageSalary(employeeArray);

この例においてToArray()の呼び出しは実装上の都合で記述されただけでありアプリケーション上の意味を持っていない。はじめからEmployee[]型が使えていれば記述の必要性はなかったコードである。また、employeeListへ追加される要素もEmployee型であることが保障されていないため、ToArray()でInvalidCastExceptionを発生する可能性がある。do somethingが短い場合は、いいが長くなってくるとemployeeListに誤った型のオブジェクトが挿入される可能性が高くなる。こうした問題を避けるため、賢明なプログラマならばEmployee型専用のコンテナを作成するだろう。その場合、CalculateAverageSalary()のインターフェイスは次のようになる。

public static float CalculateAverageSalary(EmployeeCollection employees)
{
	//do something
}

そして、その呼び出し元は次のようになるだろう。

EmployeeCollection employees = new EmployeeCollection();
//do something ...
float average = CalculateAverageSalary(employess);

ここまで実装すれば、EmployeeCollectionのインターフェイスの設計を間違えない限り実行時にInvalidCastExceptionが発生することはなくなる。しかし、ちょっとコンテナを使いたいだけなのにここまでやるのは実装コストが高い。そのため、ArrayListを使うのか配列を使うのか型付けされた専用コンテナを作成するのかで悩むのである。

Genericsの環境では、こうした問題は発生しないと言ってよいだろう。上記のコードをGenericsを使って記述すると、

public static float CalculateAverageSalary(List employees)
{
	//do something
}

呼び出し元は、

List employees = new List();
//do something
float average = CalculateAverageSalary(employees);

となる。これは、型付けされた専用コンテナを作成した場合と同程度の安全性が保障されている上にコンパイル時のコードがサイズが小さくなる*1などの特典がついてくる。

最後に、Genericsの環境でも専用コンテナがまったくいらなくなるということはではないことを付け加えておきたい。あくまで、単純なコンテナを扱いたい場合に型システムによる安全性の保障を失わずにコードを記述できることがGenericsのメリットであってアプリケーションのロジックに関わるような場合は、従来通り専用クラスを実装することになる。

*1:パラメータ化されるクラスは、その型パラメータの値に関わらず共通の実体が参照される。例えば、ListとListインスタンスが作られていた場合でも、メモリ中にList<>の実体が一つだけロードされListとListで共有される。従って、専用クラスを作成した場合に比べ専用クラスの実体分だけコードサイズが小さくなる。