Collections と Collections.Generic


みなさん、ちょっとご無沙汰してしまいました。いま、レベルメーターのちゃんとした実装を試したり、それ以外にもいくつかのネタを仕上げようとしているのですが、どれもうまく着地できずに宙ぶらりんのぶーらぶらです。そんなわけで、Google+のあるコミュニティですこし話題になった、.NET の System.Collections.Generic 名前空間と System.Collections 名前空間のコレクションがらみのデータ構造について整理して、ちょっと休憩モードです。

さて、普通に数値や文字列の固定配列ならそんなに悩むことはないわけですが、クラスの配列を動的に作ったり、スタック、メッセージキューなんかが欲しい、連想配列みたいなのが使いたいときなんかもありますよね。私は、System.Collections の ArrayList などを何気なく使っていたりしたのですが、最初にプロジェクトを作った時の using では、System.Collections.Generic だけがデフォルトになっててなんだかなぁって思ってたんです。で、あるブログの記事で Generic のほうがいいよって話を読んで、目からうろっこ!そのことについて書いていきましょう。コードは C# です。

普通の配列

普通に配列を作るときは、こんな風に書きますね。

int[] intArray=new int[10];

動的に大きさを決めたいときには、初期化しないでコードのどっかで変数で決めてもいいわけです。配列の大きさが分かってるときはこれでいいんですけれど、そういうケースって最近は少ないし、妙に大きめの値にしておいても無駄。それに配列の数を超えちゃうと実行時例外が出ますよね。実行時の例外って必ず出ればいいんですけど、まれに出るような状態だとデバッグが面倒。デバッグ環境では出ないけど、別のマシンにいったら出るってことだってあるわけですし。昔のプログラムだったら、例外が出たら急いで別の大きさを再割り当てしてコピーしたりとか例外処理コードを自分でいっぱい書いてましたよね。え?そんなの昔話だって?

もちろん、最近のプログラムではそんなことはしない。コレクションてやつを使うんですよね。最初にサイズを決めなくても勝手にうまくやってくれる便利な奴。ただ、.NET のコレクションにはどうも落とし穴があるってのが今日の話なんですよ。

System.Collectionsの落とし穴

Collections にはどんなのがあるんだっけ?ってことで調べてみると、こんなのが出てきます。

//System.Collections
ArrayList
SortedList
Hashtable
Stack
Queue

これらに共通する特徴は、ぜんぶ xxxx.Add で要素を追加できること。(Stack と Queue は違うけどね)その定義を見てみると、xxxxx.Add(object value) になってる。なんでも突っ込める魔法の袋みたい。だから、こんなコードを書いてもコンパイラに怒られたりしないんです。

public class HogeClass
{
	public string name;
	public HogeClass(string _name) 	{ name = _name; }
}
eArrayList.Add(new HogeClass("Hoge"); // これを意図してる
eArrayList.Add(new String('*',10)); // ???????

HogeClass hoge=eArrayList[0] as HogeClass;
String s=(String)eArrayList[1];

使い方をよーく理解してれば、代入するときに as するだけ。変換がうまくいかないときは、as は null が返り、cast するときは実行時例外が出ます。だけど、プログラムって作ってるうちにすっかり忘れちゃっていろんなドジをやるもんですよ。実行時エラーよりもコンパイルの段階でミスに気付きたい。だから、この何でも入るきんちゃく袋ってやつは、便利だけどバグの原因になりやすいんですよ。だから、もっと厳密なやーつを使おうってことです。

System.Collections.Genericのキビシサ

で、今度は Generic のほうをみてみましょう。

//System.Collections.Generic;
List<T>
LinkedList<T>
SortedList<TKey,TValue>
QueueT>
Stack<T>
Dictionary<TKey,TValue>
SortedDictionary<TKey,TValue>

こんなのがありますね。今度は型を宣言しないといけないので、何でも入れられるわけではない。設計するときに入れられる型を宣言しておくキビシイやつです。

Collections Generic
ArrayList List<T>
SortedList SortedList<TKey,TValue>
Hashtable Dictionary<TKey,TValue>
SortedDictionary<TKey,TValue>
Stack Stack<T>
Queue Queue<T>

だいたいこんな感じで、Collections を Generic に置き換えできそうなわけです。

余談だけど、System.Collections.Specialized にも便利な奴がいろいろあるから調べてみてね。

いろいろ突っ込みたいとき

でも、Queue なんかで2種類くらいのクラスを定義してどんどん突っ込んで、別の場所で引っ張り出したいなんて時あるよね?Tree構造なんか作るときにも、LeafとNodeを両方扱いたいとか。そんなときには抽象クラスを使うんです。Generic にバグを持ち込む可能性はありますけれど。こんな感じ。関数をそろえる時なんかは、interface でもいいよ。

public class AbClass
{
	// 関数とか・・・
}
public class HogeClass1 : AbClass
{
	protected String name;
	public HogeClass1(String _name)
	{
		name = _name;
	}
	// 関数とか・・・
}
public class HogeClass2 : AbClass
{
	protected int i;
	public HogeClass2(int _i)
	{
		i = _i;
	}
	// 関数とか・・・
}

HogeClass1 も HogeClass2 も AbClass を継承しているので、次のように使えます。Queue の例です。

Queue bug = new Queue();
bug.Enqueue(new HogeClass1("hogege"));
bug.Enqueue(new HogeClass2(3));

まあ、こんなの多用するとわけわかんなくなっていきますから、ひとつのクラスにまとめたほうがいいような気もしますが。そんなわけで、Generic をちゃんと使いましょうというような自己反省を込めた記事になりました。

この記事が、コレクションをうまく使えるようになる手助けになればいいんですけどね。

れいによって、えらい人の突っ込みご指摘をお待ちしておりますので、どうぞよろしくお願いします。

では、次回こそレベルメーターのコードを!(と言いながら、すでにできない気がしている)

参考

  • Difference between Generics and Collections with example
    http://www.dotnet-tricks.com/Tutorial/csharp/U08E301212-Difference-between-Generics-and-Collections-with-example.html

You can skip to the end and leave a response. Pinging is currently not allowed.
Subscribe to RSS Feed Twitter は Ume108 だよ