Visual Basic 初級講座 |
Visual Basic 中学校 > 初級講座 >
第28回 コレクション
今回のテーマである「コレクション」はいろいろなクラスが装備している機能です。クラスによって少しずつ性質や扱い方が異なるので少しばかりややこしい所です。今回もいくつかのクラスが登場しますので混同しないように注意してください。
この回の要約 ・コレクションを使うと似たような変数をまとめることができる。 ・コレクションは配列と比べて項目の追加が簡単。 ・コレクションは配列と違って最初に要素数を指定する必要がない。 ・ArrayListやHashtableの使い方。 |
コレクションはいくつかの変数やオブジェクトをひとまとまりに管理するための仕組みです。まとまりとして扱うだけではなくコレクションを構成する個々の要素にアクセスすること が可能です。
イメージがわかないと思いますので、とりあえずコレクションを使った簡単なプログラムの例を紹介します。
Dim
MyCollection As New ArrayList
MyCollection.Add("アラビア") MsgBox(MyCollection(2)) 'ウズベキスタンと表示される。 |
■リスト1:コレクションの簡単な例
この例では3つの文字列をまとめてMyCollectionというコレクションで管理しています。コレクションの具体的な使い方はもう少し後で解説しますがこの例を見ただけでコレクションが配列に良く似ていることに気がつかれるでしょう。
この例ではArrayList(読み方:ArrayList = アレイリスト)というコレクションを使っていますが、他にもHashtable(読み方:Hashtable = ハッシュテーブル), NameValueCollection(読み方:NameValueCollection = ネームバリューコレクション)などたくさんのコレクションがあります。
これらはどれも「コレクション」と呼ばれる種類のクラスですので、共通の性質を持っています。コレクションの特徴は項目の追加・検索が簡単なところです。以降ではこれらの点を中心に話を進めていきます。
コレクションはいたるところに登場します。たとえば、フォームやパネルのControlsプロパティはコレクションですし、リストボックスの項目もコレクションです。
このように既に作成されているコレクションを利用することもありますし、独自のコレクションを作成して利用することもできます。独自のコレクションを利用するには、ArrayListやHashtableなどのクラスを使用します。代表的なものを表にまとめてみました。
クラス | 読み方 | キー | インデックス | 説明 |
ArrayList | アレイリスト | × | ○ | もっとも基本的なコレクション。 |
Hashtable | ハッシュテーブル | ○ | × | |
SortedList | ソーテッドリスト | ○ | ○ | ArrayListとHashtableを合体させたようなコレクション。 ただし、インデックスは自動的にキーの辞書順になります。 |
NameValueCollection | ネームバリューコレクション | ○ | ○ | ArrayListとHashtableを合体させたようなコレクション。 ただし、文字列型のみを扱う。 |
■表1:主なコレクション
これらのクラスは今回の解説の中心でもあります。
同じような機能を持ったコレクションがどうしていくつも種類があるのかというと、それはどのように要素を管理するかが異なるからです。たとえば、配列のように数値のみを使って要素にアクセスできれば十分であるならArrayListを使用します。 この場合プログラマは簡単に項目を追加できる便利な配列を使う感覚でプログラムできます。しかも、配列と違ってあらかじめ要素数の上限を指定する必要もありません。数値ではなく文字列を使って要素にアクセスしたい場合はHashtablbeの使用を検討します。NameValueCollectionは 数値・文字列の両方を使って要素にアクセスできます。
この他にもビット値を専門に扱うBitArray(読み方:BitArray = ビットアレイ)や、要素の取出し方に特徴のあるQueue(読み方:Queue = キュー)やStack(読み方:Stack = スタック)など、さまざまなコレクションが用意されています。
コレクションに対してはFor Eachによるループを使用することができます。For Eachは配列とコレクション専用のループ方式で、コレクションであればFor Eachが使用できます。初級講座では既に何度かFor Eachが登場しましたがここであらためて説明します。
For Eachはコレクションの要素を1つずつ取り出して処理を行う場合のループです。たとえば、次のコードでは、リストボックスにコレクションの内容と、それを元に生成した内容を表示します。
Dim Ar
As
New ArrayList Dim St As String Dim NewType As String Dim OldType As String
Ar.Add("国") For
Each St
In Ar |
■リスト2:新旧漢字対応表
このコードではコレクションの要素である漢字とその旧字体を並べてリストボックスに表示するようにFor Eachで処理しています。ループの中でStはコレクションの構成要素を指しています。ループの一周目ではStは「国」です。二周目では「学」です。
■画像1:リスト2の実行結果
ここで変数Stが文字列型であることに注意してください。これはコレクションの要素が文字列型だからです。もし、コレクションの要素がButtonである場合はやはりButton型の変数を用意しなければなりません。
次のコードはButtonを文字列型に変換できないためにInvalidCastExceptionが発生します。
'この例はエラーになります。
Dim Ar
As
New ArrayList Ar.Add(Button1) For
Each St
In Ar |
■リスト3:型変換に失敗する例
この例を正しく動作させるには、Button型の変数を用意してループに使用します。
Dim Ar
As
New ArrayList Dim btnItem As Button Ar.Add(Button1) For
Each btnItem
In Ar |
■リスト4:正しく型指定している例
ループの変数(カウンタ変数)はFor Eachと同時に宣言することもできて、その場合その変数の適用範囲はFor Eachブロック内になります。
Dim
Ar As
New ArrayList
Ar.Add(Button1) For
Each btnItem
As Button
In Ar |
■リスト5:ブロックスコープの変数を利用する例
ただし、Hashtableのようにコレクションが値の他にキーも保持している場合はループの変数にはキーと値のペアを格納するために 必ずDictionaryEntry型(読み方:DictionaryEntry = ディクショナリーエントリー)を指定する必要があります。
Dim SL
As
New Hashtable Dim Item As DictionaryEntry
SL("あ") = "愛知" For
Each Item
In SL |
■リスト6:HashtableでのFor Each
しかしながらNameValueCollectionではこの方法はエラーになるなどコレクションによって扱い方が異なりますので注意してください。
参考 旧字体の取得について リスト2で漢字の旧字体を取得するプログラムを紹介しましたが、全ての漢字でうまくいくわけではありません。このプログラムの実体は現代の漢字を中国語の簡体字にみたてて対応する繁体字を取得しているだけです。 私も中国語のことはよくわからないのですがリスト2で例示した漢字はうまい具合に日本の新字体=中国の簡体字、日本の旧字体=中国の繁体字となっている漢字のようです。なお、サンプルはWindowsXPでフォント「MS UI Gothic」です。 |
コレクションの基本は項目の追加と取得、それに検索にあります。
AddメソッドやItemプロパティ(読み方:Item = アイテム)、またはコンストラクタを使って項目を追加し、For EachやItemプロパティなどを使って項目を取得して利用します。
項目が大量にある場合はContainsメソッド(読み方:Contains = コンテインズ)を使用すれば目的に項目が存在するかどうかすぐにわかりますし、IndexOfメソッド(読み方:IndexOf = インデックスオブ)を使えば目的の項目をすぐに取得することができます。
コレクションにもいろいろなるの全てのコレクションで上記のような操作ができるわけではありませんが、これらは基本的な操作ですので覚えておくべきでしょう。
次からはこれらの操作を簡単に説明した後で、よく使ういくつかのコレクションについて主な点を説明します。
多くのコレクションでは項目を追加するのにAddメソッドやItemプロパティ、それにコンストラクタを使用します。この他の方法で項目を追加できる場合もあります。ArrayListでのAddメソッドの使用例はすでに紹介しましたので、ここではHashtableでのAddメソッドを紹介します。
Dim
Names As
New Hashtable
Names.Add("江戸幕府", "徳川家康") MsgBox(Names("鎌倉幕府")) |
■リスト7:項目追加の例
Hashtableでは 項目を追加するためのAddメソッドは2つの引数をとります。ArrayListでは単純に追加したい項目を指定するだけでしたがHashtableではそうではありません。1つ目の引数は「キー」と呼ばされるものでこれが検索の要となります。同じキーで複数の項目を登録することはできません。2つ目の引数が 追加する項目の値です。
最後に表示しているMsgBoxではキー「鎌倉幕府」に該当する値として「源頼朝」が表示されます。
このようにHashtableでは追加したい項目の値だけではなく、それとペアになる項目の「キー」を指定します。そして、そのキーを利用して項目を取得することができるのです。
Itemプロパティを使って項目を登録するには次のようにします。ArrayListではItemプロパティを使って項目を登録することはできません。
Dim
Names As
New Hashtable Names.Item("江戸幕府") = "徳川家康" |
■リスト8:項目追加
さらに、Itemプロパティは省略可能なので、多くのプログラマは実際には次のように記述します。
Dim
Names As
New Hashtable
Names("江戸幕府") = "徳川家康" MsgBox(Names("鎌倉幕府")) |
■リスト9:Itemプロパティの省略
最後にコンストラクタを使用する例を紹介します。コレクションの種類にもよりますがコンストラクタを使用すると既にある配列などを元にコレクションを作成することができます。
Dim
Names() As String = {"アムロ",
"カミーユ", "キラ"} Dim Ar As New ArrayList(Names) |
■リスト10:コンストラクタの利用
この例ではArrayListを使用しています。Hashtableではこの方法は使えません。
コレクションから項目を取得するにはFor Eachを使用するか、Itemプロパティを使用します。この他の方法で項目を取得できる場合もあります。
Itemプロパティで項目を取得するときは目的の項目のインデックスかキーを指定します。インデックスは項目を追加した順番に自動的に0から数値が割り当てられています。
次の例ではArrayListの2番目の項目(=インデックス1)を取得して表示します。
Dim Ar
As
New ArrayList(New
String()
{"Apple", "Banana", "Cat", "Dog"}) Dim Item As String Item = Ar.Item(1) MsgBox(Item) |
■リスト11:インデックスを利用した項目取得
Itemプロパティは省略可能ですので、この例は次のように書くこともできます。実際のプログラマの多くは以下の例のようにItemプロパティを省略して書きます。
Dim Ar
As
New ArrayList(New
String()
{"Apple", "Banana", "Cat", "Dog"}) Dim Item As String Item = Ar(1) MsgBox(Item) |
■リスト12:Itemプロパティの省略
なお、HashtableではItemプロパティにインデックスを指定することはできません。Hashtableのように文字列型のキーを使用するコレクションであればItemプロパティにインデックスではなくキーを指定することができます。
Dim
Names As
New Hashtable Dim St As String
Names("江戸幕府") = "徳川家康" St = Names("鎌倉幕府") MsgBox(St) |
■リスト13:キーを利用した項目取得
この例では初めからItemプロパティを省略しています。
「検索」というと地味でつまらない機能という印象を持たれるかもしれませんが、コレクションの検索は単純明快です。時にはこの検索機能のためだけにコレクションを使う場合もあります。
すぐ前に書いたItemプロパティを使ってキーを指定する方法も検索の一種ということができます。キーによる検索はかなり高速で動作するのが特徴です。(とは言え、インデックスによる項目の取得の方がやはり高速です。)
キーによる検索はキーを備えているコレクションでしか使えないのでArrayListでは無理ですが、IndexOfメソッドなどを使用すればArrayListも含めていろいろなコレクションで目的の値を持つ項目を取得することができます。
Dim
Ar As
New ArrayList(New
String()
{"Apple", "Banana", "Cat", "Dog"}) Dim Index As Integer Dim Item As String
Index = Ar.IndexOf("Cat") MsgBox(Item) |
■リスト14:インデックスの検索
ただし、IndexOfメソッドは文字通り目的の項目のインデックスを取得するだけなので、項目自体を取得するためにさらにItemプロパティなどを使う必要があります。IndexOfメソッドはコレクションの先頭から検索を開始しますが、LastIndexOfメソッド(読み方:LastIndexOf = ラストインデックスオブ)は最後から検索します。
なお、IndexOfメソッドもLastIndexOfメソッドも項目が見つからなかった場合は -1 を返します。
Containsメソッド(読み方:Contains = コンテインズ)は単に項目が存在するかどうかを調べます。
Dim Ar
As
New ArrayList(New
String()
{"Apple", "Banana", "Cat", "Dog"}) Dim Index As Integer Dim Item As String If Ar.Contains("Cat") Then MsgBox("Catは存在します。") Else MsgBox("Catは存在しません。") End If |
■リスト15:項目の存在確認
キーの扱いも知っておくと便利です。ArrayListにはキーはありませんが、HashtableやNameValueCollectionなどにはキーが存在します。既に説明したようにキーは項目を特定するための文字列型の値で、項目を追加するときに指定します。
Hashtableを使って 次のように、項目を追加する場合を考えて見ます。
Dim
Names As
New Hashtable
Names("江戸幕府") = "徳川家康" |
■リスト16
この場合Namesには4つの項目があり、それぞれにキーと値があることになります。
キー | 値 |
江戸幕府 | 徳川家康 |
室町幕府 | 足利尊氏 |
鎌倉幕府 | 源頼朝 |
大和朝廷 | 神武天皇 |
■表2:リスト16で作成されるコレクションの内容
後はキーを指定して、St = Names("鎌倉幕府") のようにすれば、目的の「源頼朝」という値が得られるわけです。
NameValueCollectionはキーとインデックスの両方を保持しています。NameValueCollectionで次のコードで生成されるコレクションの内容を考えて見ます。
Dim
Names As
New Specialized.NameValueCollection
Names("江戸幕府") = "徳川家康" |
■リスト17
この結果コレクションの内容は次のようになります。
インデックス | キー | 値 |
0 | 江戸幕府 | 徳川家康 |
1 | 室町幕府 | 足利尊氏 |
2 | 鎌倉幕府 | 源頼朝 |
3 | 大和朝廷 | 神武天皇 |
■表3:リスト17で作成されるコレクションの内容
ここからはHashtableの場合と同様にキーを使って、 St = Names("鎌倉幕府") のようにして「源頼朝」という値を得ることもできますし、インデックスを使って、 St = Names(2) として「源頼朝」を得ることもできます。
多くのコレクションではキーに対する操作を提供するメソッドやプロパティも用意されています。たとえば、ContainsKeyプロパティ(読み方:ContainsKey = コンテインズキー)を使うと指定したキーが存在するかわかりますし、Keysプロパティ(読み方:Keys = キーズ)を使うとキーだけの一覧を取得することができます。
ほとんどのコレクションでは同じキーで2つ目の項目を追加しようとするとエラーになります。NameValueCollectionでは同じキーで複数の項目を登録することができますがこれは例外的な処理です。
項目の追加・取得・検索以外にも各コレクションはいろいろな動作をサポートしている場合があります。
代表的なところを表にまとめておきます。コレクションによってはこれらのメソッドやプロパティを持っていないものもありますし多少機能が違うものもあります。
メソッド・プロパティ | 読み方 | 説明 |
AddRange | アドレンジ | 配列の項目を要素として追加します。 |
Clear | クリア | すべての項目を削除します。 |
Clone | クローン | コレクションのコピーを作成します。 |
Count | カウント | 項目の数を表します。 |
Insert | インサート | 項目を途中に追加します。 |
Keys | キーズ | キーのコレクションを表します。 |
Remove | リムーブ | 項目を1つ削除します。 |
ToArray | トゥーアレイ | コレクションの内容を配列として返します。 |
■表4:コレクションを操作する代表的なメソッド・プロパティ
博士のワンポイントレッスン
|
一通りコレクションの説明が終了しましたのでよく出てくるコレクションについてまとめておきます。
ここでも主な事柄をまとめるだけなのでここに書いていないこともあることは注意して下さい。
まずはArrayListです。ArrayListはもっとも基本的なコレクションで、どのコレクションを使用すべきか迷ったらArrayListを採用します。
項目の追加 | Add, AddRange, Insert, コンストラクタ |
Itemプロパティの引数 | インデックス |
項目の検索 | IndexOf, LastIndexOf, Contains |
ArrayListはAddメソッドを使用して簡単に項目を追加することができ、項目にアクセスするには数値型のインデックスを使用します。このインデックスは項目を追加した順番を表す0から始まる整数です。これだけのシンプルな機能をシンプルに使用できるのがArrayListの魅力です。もちろんこの他のメソッドやプロパティも備えていてなかなか融通が利きます。
Hashtableは文字列型のキーが必要な場合によく使われるコレクションです。Hashtableでは数値型のインデックスを使用することはできません。
項目の追加 | Add, Item |
Itemプロパティの引数 | キー |
項目の検索 | Contains |
SortedListはArrayListとHashtableの両方の機能を合体させたようなコレクションです。Hashtableよりも柔軟性があります。
項目の追加 | Add, Item |
Itemプロパティの引数 | キー ※GetByIndexプロパティでインデックスから項目を取得することもできる。 |
項目の検索 | Contains |
Itemプロパティには文字列型のキーしか指定できませんが、GetByIndexプロパティ(読み方:GetByIndex = ゲットバイインデックス)を使うとインデックスを指定して項目を取得することもできます。ただし、SortedListのインデックスはキーの辞書順になります。後から追加して項目でも辞書順で並び替えられてインデックスが割り当てられます。
NameValueCollectionもキーとインデックスの両方を使って項目にアクセスできます。インデックスは項目の追加順になります。ただし、NameValueCollectionでは項目の値は文字列型しか利用できません。
項目の追加 | Add, Item |
Itemプロパティの引数 | キー、インデックス |
項目の検索 |
この他にもNameValueCollectionは他のコレクションとは違う独特の機能があります。
今まで説明してきたコレクションの要素の値はすべてObject型でした。これは文字列でも数値でもテキストボックスでもその他のクラスでも何でもコレクションで管理できて便利ではあるのですがとりだすときに不便です。
次の2つのコードを実行して比較してみてください。
Dim
Ar As New
ArrayList Dim i As Integer Dim StartTime As Long TextBox1.Text
= 0 Ar(0).Text = Ar(0).Text + 1 Next ListBox1.Items.Add(Now.Ticks - StartTime) 'かかった時間を表示 |
■リスト18:自動型変換
Dim
Ar As New
ArrayList Dim i As Integer Dim StartTime As Long TextBox1.Text
= 0 CType(Ar(0), TextBox).Text = CType(Ar(0), TextBox).Text + 1 Next ListBox1.Items.Add(Now.Ticks - StartTime) 'かかった時間を表示 |
■リスト19:手動型変換
この2つのコードはコレクションに追加したテキストボックスに101回加算を実行する例です。実行後にはテキストボックスには101と表示されます。
上の例は何も気にしないで素直に書いた例、下の例はコレクションの要素のアクセスするときにTextBox型に変換を行うように書いた例です。実行にかかった時間がリストボックスに表示されるようにしてあるので実際に試してみるとわかるのですが、上の例の方が実行が遅いです。しかもちょっとやそっとではなく5〜10倍も遅いです。VBは明確に型を指定した方が早く動作するのです。この違いのためにコレクションを下手に使うと体感できるほどの遅さが発生する場合があります。
一般に型を指定していない場合以下のデメリットが発生します。
・実行が遅い
・「 . 」を入力したときにインテリセンスで入力候補の一覧が表示されない。
・ビルド時にスペルミス等が検出されないので実行するまでエラーかどうかわからない。
コレクションだからといって型を指定しないで済ますことはお勧めできません。型を強制的に指定するには上記の例のようにCType(読み方:CType = シータイプ)などにより型変換を命令します。
VB2005以降では要素やキーの型を指定できるコレクションが追加されています。これらのコレクションはSystem.Collections.Generic名前空間(読み方:Generic = ジェネリック)にあります。たとえば、テキストボックスを要素に持つコレクションを作成するには次のようにします。この例ではList(読み方:List = リスト)というコレクションを使用しています。
Dim List
As New
Generic.List(Of TextBox) Dim i As Integer Dim StartTime As Long TextBox1.Text
= 0 List(0).Text = List(0).Text + 1 Next ListBox1.Items.Add(Now.Ticks - StartTime) 'かかった時間を表示 |
■リスト20:型指定したコレクションの利用
この例のように型指定が可能な場合には宣言するときに Of (読み方Of = オブ)の後ろに型を指定します。この指定方法はコレクション以外でも型指定可能なクラスすべてに共通です。
今回はコレクションの概要について説明したつもりですが、コレクションにはいろいろな種類があり、扱い方も少しずつ違いますからうまく説明できているか心配です。
最後にコレクションの比較を表にまとめておきますのでこれも参考にしてください。
ArrayList | Hashtable | SortedList | 配列 | |
インデックス | ○ | × | ○ | ○ |
キー | × | ○ | ○ | × |
Add | ○ | ○ | ○ | × |
AddRange | ○ | × | × | × |
Clear | ○ | ○ | ○ | ○ |
Clone | ○ | ○ | ○ | ○ |
Contains | ○ | ○ | ○ | × |
Count | ○ | ○ | ○ | × |
IndexOf | ○ | × | × | △ |
Insert | ○ | × | × | × |
Keys | × | ○ | ○ | × |
LastIndexOf | ○ | × | × | △ |
Remove | ○ | ○ | ○ | × |
ToArray | ○ | × | × | × |
■表4:コレクションの比較