Visual Basic 中学校 初級講座
VB2005 対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Visual Basic 中学校 > 初級講座 >

第28回 コレクション

2016/1/3 更新

この記事が対象とする製品・バージョン (バージョンの確認方法)

VB2015対応 Visual Basic 2015 対象です。
VB2013対応 Visual Basic 2013 対象です。
VB2012対応 Visual Basic 2012 対象です。
VB2010対応 Visual Basic 2010 対象です。
VB2008対応 Visual Basic 2008 対象です。
VB2005 対応 Visual Basic 2005 対象です。
VB.NET 2003 対応 Visual Basic.NET 2003 × 対象外ですが、こちらで説明しています。VB2003版 第28回 コレクション
VB.NET 2002 対応 Visual Basic.NET (2002) × 対象外ですが、こちらで説明しています。VB2003版 第28回 コレクション
VB6対応 Visual Basic 6.0 × 対象外ですが、こちらで説明しています。VB6版 第23回 コレクション

 

概要
  • コレクションを使うと似たような変数をまとめることができる。
  • コレクションにはList、Dictionaryなどいろいろな種類がある。
  • コレクションは配列とくらべて項目の追加・削除が簡単。
  • コレクションは配列と違って最初に要素数を指定する必要がない。

1.コレクションの概要

1−1.簡単な例

コレクションとは、いくつかの変数やオブジェクトをひとまとまりに管理するための仕組みです。まとまりとして扱うだけではなくコレクションを構成する個々の要素にアクセスすることが可能です。

イメージがわかないと思いますので、まずはコレクションを使った簡単なプログラムの例を紹介します。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim countries As New List(Of String)

countries.Add("アラビア")
countries.Add("インド")
countries.Add("ウズベキスタン")

MsgBox(countries(2)) 'ウズベキスタンと表示される。

■リストA-1:コレクションの簡単な例

この例では「アラビア」、「インド」、「ウズベキスタン」という3つの文字列をまとめてcountriesという1つのコレクションで管理しています。

1−2.配列との違い

リストA-1の例を見るとコレクションが配列と良く似ていることに気がつかれるでしょう。

しかし、配列のときに扱いが面倒だった点が改善されておりとても使いやすくなっています。

比較のために、リストA-1と同じことを配列で書いてみた例を載せます。

VB6対応 VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim countries(2) As String

countries(0) = "アラビア"
countries(1) = "インド"
countries(2) = "ウズベキスタン"

MsgBox(countries(2)) 'ウズベキスタンと表示される。

■リストA-2:配列でA-1と同じプログラムを書いてみる

まず、配列は使う前に要素数を決める必要がありましたが、コレクションは自動的に要素数を拡張するためプログラマーは何もする必要がありません。これは配列だとRedimが必要になる場合特に面倒なのですが、コレクションにはRedimの心配もないということです。

次に、項目を追加するとき、配列は必ずインデックスを指定する必要がありましたが、コレクションは単純にメソッドを呼び出すだけで追加できます。使用するメソッドはコレクションの種類によって異なりますが、この例でh使用しているList(読み方:リスト)の場合はAddメソッド(読み方:アド)です。

最後にウズベキスタンを表示するところでは、コレクションも配列も同じです。インデックス(添え字)を使って、項目を取得することができます。

項目の取得方法はコレクションの種類によって異なるので、インデックスを使って項目を取得できないコレクションもあります。

1−3.説明の進め方

配列と違ってコレクションにはいろいろな種類があります。コレクションはどれも共通の性質を持っており、だいたいの使い方を理解していればはじめて見るコレクションでも使うことができるようになります。

今回は代表的なコレクションであるList、Dictionary(読み方:ディクショナリー), Stack(読み方:スタック), Queue(読み方:キュー)の説明をしたうえで、コレクションに共通に当てはまる操作の説明をします。

メモ メモ - コレクションは List が圧倒的に人気No.1

私の個人的な感覚ですが、コレクションにはいろいろな種類があるとはいえ、自分のプログラムで使う場合はListを使うケースが90%、のこり9%がDictionary。そして、1%をさまざまなコレクションで分け合っているというイメージです。

コレクション使用率

2.List

2−1.Listの概要

Listはシンプルで汎用的なコレクションです。Listを使えば配列でできることはほとんどが可能であり、しかも配列より使いやすいです。

2−2.インスタンスの作成

Listのインスタンスを作成するときには、Of (読み方:オブ)を使ってまとめて管理する対象の項目の型を指定します。

文字列型の項目をまとめるコレクションを作る場合、Listのインスタンスは次のように作成します。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応


Dim
myList As New List(Of String)

■リストB-1

Integer型の項目をまとめるコレクションを作成するには次のようにします。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応


Dim
myList As New List(Of Integer)

■リストB-2

このようなに型を引数のように指定する機能を「型パラメーター」と呼びます。型パラメーターを指定する場合は必ずOf を使用します。

コレクションの場合、型パラメーターには特に制約がなくどのようなクラス・構造体でも設定できます。たとえば、Buttonのまとまりを管理するには New List(Of Button) を書くことができますし、自作のクラスを指定することもできます。

2−3.コレクション初期化子

変数を宣言するときに、初期値を設定することを初期化と呼びます。文字列や数値の場合は Dim の行で後ろに = で初期値を設定できます。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応


Dim x As Integer = 627

■リストB-3:通常の変数の初期化

Listでも同じように初期化ができます。VB2010以上の場合は、新しいコレクション初期化子という機能を使って、コレクション専用の記述方法でコレクションの項目を指定して初期化ができます。これにはキーワード From (読み方:フロム)を使って次のように記述します。

VB2010対応 VB2012対応 VB2013対応 VB2015対応


Dim countries As New List(Of String) From {"アラビア", "インド", "ウズベキスタン"}

■リストB-4:コレクションの初期化

2−3.項目の追加

2−3−1.Addメソッド 項目を1つ追加

通常、 項目を追加するにはAddメソッドを使用します。

追加できるのは、Listのインスタンスを作成したときに型パラメーターで指定した型と、その派生型です。

次の例は文字列型の項目を格納できるListの使用例です。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim stars As New List(Of String)

stars.Add("地球")
stars.Add("火星")
stars.Add("スピカ")
stars.Add("ベテルギウス")

'Listの内容を表示
MsgBox(String.Join(vbNewLine, stars.ToArray))

■リストB-5:

おまけで、最後にListの内容を表示するようにしてみました。この最後の行は配列の各要素を1つの文字列として結合するStringクラスのJoinメソッド(読み方:ジョイン)を利用し、間に改行(vbNewLine)をはさんで1つの文字列にしています。ListクラスのToArrayメソッド(読み方:トゥーアレイ)は、リストを配列に変換する機能があります。.NET Framework 4以上(VB2010以上)であれば、Joinメソッドの機能が拡張され、第2引数にListを直接渡すこともできるようになっているのでToArrayがなくても動作します。

 

さて、型パラメーターには制限がないので、さまざまなオブジェクトを格納できるListを作成することもできます。

たとえば、フォーム上に存在するいくつかのボタンをグループ化したい場合は型パラメーターにButtonを指定します。

(VB2005対応) VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim buttons As New List(Of Button)

buttons.Add(Button1)
buttons.Add(Button3)
buttons.Add(Button4)

'Listに格納されているボタンをすべて無効にする。(この行はVB2005では動きません。)
buttons.ForEach(Sub(button) button.Enabled = False)

■リストB-6:

この例の最後では、おまけでListに格納されているボタンをすべて無効にするプログラムを追加しています。このようにコレクションとして管理されている項目をまとまりとして操作できるのも便利です。この最後行はVB2008から導入されたラムダ式という機能を使っているためここだけVB2005では実行できません。ラムダ式については別の機会に説明します。

では、Buttonも格納したいし、TextBoxやListBoxも格納したいという場合はどうしたらよいでしょうか。型パラメーターにButtonやTextBoxを指定するわけにいきません。

この場合は、ButtonやTextBoxの共通の基底クラスであるControlを指定することで解決できます。

なお、どのようなクラスであれ、すべてObjectから派生しているので、型パラメーターにObjectを指定すればどのようなクラスでも格納できるListを作成することは可能です。しかし、型が明示的にそろっていることにはいろいろなメリットがあるため型パラメーターはできるだけ限定したものを使用するようにしましょう。

博士のワンポイントレッスン 「基底クラス・派生クラス」
V太 V太:博士〜。「基底クラス」とか「派生」ってなんでしたっけ?
博士 博士:実は初級講座 第17回 コントロールの基本共通機能の最初のほうで説明しておるのじゃ。
B子 B子:簡単に言うとね、.NETではすべてのクラスが階層化されていて親子関係があるのよ。親のことを「基底クラス」、子供のことを「派生クラス」というのよ。親をどんどんたどっていくとどのクラスもObjectに行き着くの。
博士 博士:ここでは「階層化」と言っているが、正式には「継承」という機能なのじゃ。

次の例は、型パラメーターにControlを指定し、いろいろコントロールをListに追加する例です。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim controlList As New List(Of Control)

controlList.Add(Button2)
controlList.Add(TextBox2)
controlList.Add(ListBox1)

■リストB-7:

なお、フォームにはフォームに直接貼り付いているコントロールを表すControlsプロパティ(読み方:コントロールズ)があり、これもコレクションです。

2−3−2.AddRangeメソッド 項目を複数追加

AddRangeメソッドは引数に配列かコレクションを指定することで複数の項目を一度に追加することができます。

複数の項目は配列またはコレクションとして指定します。

次の例では { } による配列リテラルを利用して項目を追加しています。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim stars As New List(Of String)

stars.AddRange({"地球", "火星", "スピカ", "ベテルギウス"})

■リストB-8:

もう1つ、配列リテラルを使用しない例を紹介します。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim planets As New List(Of String)
planets.Add("地球")
planets.Add("火星")

Dim fixedStars As New List(Of String)
fixedStars.Add("スピカ")
fixedStars.Add("ベテルギウス")

Dim stars As New List(Of String)
stars.AddRange(planets)
stars.AddRange(fixedStars)

■リストB-9:

実際に使うとしたら、リテラルを使わない書き方の方が多くなると思いますが、サンプルプログラムを簡単に書くには配列リテラルを使うやり方の方が楽です。

だいたい、この例だけ見ると何のために手間をかけてAddRangeを使っているかよくわからないでしょう。AddRangeを使いたくなる場合は、どこかで既に作成済みの配列やコレクションの内容をそのまま取り込みたいときなのです。

2−3−3.Insertメソッド 項目を挿入

AddメソッドをListの最後にどんどん項目を追加していくのに対して、Insertメソッドは位置を指定して項目を追加することができます。

AddRangeに対応するInsertRangeメソッド(読み方:インサートレンジ)もあります。InsertRangeを使用すると、指定した位置に複数の項目を挿入することができます。

なお、Listに大量の項目を格納している場合、途中に項目を挿入するしたり、次で説明する削除機能を使って、途中の項目を削除する性能は悪いです。

もし、項目の挿入や削除が頻繁にあり、大量の項目を維持する可能性がある場合は、Listではなく、LinkedList(読み方:リンクドリスト)の使用を検討してください。LinkedListは挿入や削除はきわめて高速に動作しますが、個々の項目を単純に取得する性能は悪いです。

2−4.項目の削除

Listから指定した項目を削除するにはRemoveメソッド (読み方:リムーブ)を使用します。

指定した位置にある項目を削除するにはRemoveAtメソッド (読み方:リムーブアット)を使用します。

すべての項目を削除するにはClearメソッド (読み方:クリア)を使用します。

次の例では、「火星」をListから削除します。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim stars As New List(Of String)

stars.Add("地球")
stars.Add("火星")
stars.Add("スピカ")
stars.Add("ベテルギウス")

stars.Remove("火星")

'Listの内容を表示
MsgBox(String.Join(vbNewLine, stars.ToArray))

■リストB-10:

Listから削除するということは、Listからなくなるだけで、別の場所でその項目を使用している場合、その項目自体がなくなるわけではありません。たとえば、ButtonをListからRemoveしてもフォーム上からボタンがなくなるわけではありません。

次の例では、3番目に追加した項目をListから削除します。プログラムでは1番目の項目は 0 なので、3番目の項目を指定するには 2 を指定します。このような数字をインデックスと呼びます。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim stars As New List(Of String)

stars.Add("地球")
stars.Add("火星")
stars.Add("スピカ")
stars.Add("ベテルギウス")

stars.RemoveAt(2)

'Listの内容を表示
MsgBox(String.Join(vbNewLine, stars.ToArray))

■リストB-11:「スピカ」が削除される。

2−5.項目を1つ取得

格納した1つ項目を取得するにはItemプロパティ(読み方:アイテム)にインデックスを指定します。

インデックスは追加した順に0から振られる連番です。途中の項目が削除された場合は、それ以降の項目のインデックスが1ずつ小さくなって連番を維持します。

次の例は「火星」を取得します。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim stars As New List(Of String)

stars.Add("地球")
stars.Add("火星")
stars.Add("スピカ")
stars.Add("ベテルギウス")

Dim myStar As String
myStar = stars.Item(1)

MsgBox(myStar)

■リストB-12:

Itemプロパティは省略可能なので、stars.Item(1) と書く代わりに stars(1) と簡略に書くことができます。ほとんどのプログラマはItemプロパティを省略して簡略に記述します。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim stars As New List(Of String)

stars.Add("地球")
stars.Add("火星")
stars.Add("スピカ")
stars.Add("ベテルギウス")

Dim myStar As String
myStar = stars(1)

MsgBox(myStar)

■リストB-13:

2−7.その他

Listに含まれる項目をすべて列挙する機能がありますが、この列挙機能はコレクションに共通する最大の特徴なので別の章をで改めて説明します。

その他にもListにはさまざまな機能があります。自分でListを使ってプログラムする前に必要な機能がすでに実装されているかリファレンスで調べてみることをお勧めします。

ここでは良く使うものを2つ紹介します。

Containsメソッド (読み方:コンテインズ)を使用すると、指定した項目が既にListに含まれているか調べることができます。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

If stars.Contains("冥王星") Then
    MsgBox("冥王星は含まれています。")
End If

■リストB-14:

IndexOfメソッド (読み方:インデックスオブ)を使用すると、指定した項目がListの何番目に最初に出現するかインデックスを取得することができます。このメソッドは項目がListに含まれていない場合 -1 を返すので Containsメソッドの代わりにもなります。

3.Dictionary

3−1.Dictionaryの概要

Dictionaryは Listの次のよく使うコレクションです。ListとDictionaryが使えれば自分でコレクションを作るときはほぼ十分です。後は知識としてStack (読み方:スタック)とQueue (読み方:キュー)をおさえておけばよいでしょう。

DictionaryはListと違って、ただ単に項目をまとまりとして管理するだけではなく、国語辞典のように各項目に見出しをつけ、見出しを使って個々の項目にアクセスできます。

この見出しのことをキーと呼びます。

Dictionaryの項目 = キー と 値

辞書の写真

■画像:Dictionaryのイメージ

そのため、項目を追加するときにキーと値の2つを指定する必要があります。

人間が使う辞典ではキーも値も文字列ですが、プログラムの世界なので、いろいろな型が使用でき、Listと同じく型パラメーターを使ってキーの型と値の型を指定します。

最初の型パラメーターがキーの型、2番目の型パラメーターが値の型です。

たとえば、キーが文字列型、値も文字列型のDictionaryは次のように作成します。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応


Dim myDic As New Dictionary(Of String, String)

■リストC-1:

キーが日付型、値が画像(Image)の場合は、次のようにします。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応


Dim myDic As New Dictionary(Of Date, Image)

■リストC-2:

Dictionaryの項目は型パラメーターに何を指定しても、KeyValuePair型(読み方:キーバリューペア)です。Dictionaryの型パラメーターは項目の型を指定しているのではなく、キーの型と値の型を指定していることになります。

3−2.コレクション初期化子

Dictionaryでもコレクション初期化子を利用することができます。Listと同様VB2010以上が必要です。

しかし、コレクションの値がはじめから決まっていることはほとんどない上に、見かけも複雑なので、サンプルでときどき見かける以外は使っている人を見たことがありません。

Dictionaryの場合は、キーと値の両方を指定する必要があるので、初期化も少し複雑です。

例を示します。

VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim names As New Dictionary(Of String, String) From
    {
        {"A", "Apple"},
        {"B", "Banana"},
        {"C", "Cat"}
    }

■リストC-3:

1行で書くこともできるのですが、キーと値がごちゃごちゃしてわかりにくくなるので、ここでは改行しました。

この例を実行すると下記の3つの項目をもったDictionaryが作成されます。

  キー
項目1 A Apple
項目2 B Banana
項目3 C Cat

キーの型はString、値の型はString、項目の型はKeyValuePairです。

3−2.項目の追加

Dictionaryに項目を追加するにはAddメソッドを使用します。この点はListと同じで、他の多くのコレクションでも共通しています。

Dictionaryの場合は、項目を追加するときにキーと値の2つを指定する必要があるのでAddメソッドにこの2つを設定します。

次の例はDictionaryに項目を追加する例です。Addメソッドの第1引数がキーで、第2引数が値です。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim names As New Dictionary(Of String, String)

names.Add("江戸幕府", "徳川家康")
names.Add("室町幕府", "足利尊氏")
names.Add("鎌倉幕府", "源頼朝")
names.Add("大和朝廷", "神武天皇")

'源頼朝 を表示します。
MsgBox(names("鎌倉幕府"))

■リストC-4:項目追加の例

この例ではDictionaryに4つの項目を追加しています。各項目ではキーに幕府の名前などを設定し、値にその開設者といわれている人物の名前を指定しています。

  キー
項目1 江戸幕府 徳川家康
項目2 室町幕府 足利尊氏
項目3 鎌倉幕府 源頼朝
項目4 大和朝廷 神武天皇

最後に表示しているMsgBoxではキー「鎌倉幕府」に該当する値として「源頼朝」が表示されます。このように値の取得方法はListとは異なります。

 

Dictionaryの場合、Itemプロパティを使って項目を追加することもできます。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim names As New Dictionary(Of String, String)

names.Item("江戸幕府") = "徳川家康"

■リストC-5:項目追加の例

さらに、Itemプロパティは省略可能なので、次のように記述することもできます。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim names As New Dictionary(Of String, String)

names("江戸幕府") = "徳川家康"
names("室町幕府") = "足利尊氏"
names("鎌倉幕府") = "源頼朝"
names("大和朝廷") = "神武天皇"

'源頼朝 を表示します。
MsgBox(names("鎌倉幕府"))

■リストC-6:項目追加の例

この例は省略しているだけでItemプロパティを使っていることに変わりありません。

Itemプロパティを使うと追加だけでなく、追加済みの値の変更を行うこともできます。Addメソッドの方は追加専用なので値の変更を行うことはできません。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim names As New Dictionary(Of String, String)

names("大和朝廷") = "神武天皇"
names("大和朝廷") = "雄略天皇"

'雄略天皇 を表示します。
MsgBox(names("大和朝廷"))

■リストC-7:値の変更の例

この例では、はじめ「大和朝廷」のキーの値を「神武天皇」で登録していますが、そのすぐ次の行で「雄略天皇」に変更しています。結果として雄略天皇が表示されます。

Addメソッドでは同じキーを登録しようとするとArgumentException (読み方:オーギュメントエクセプション)の例外が発生します。さきほど説明した国語辞典のイメージで、同じキーが2つあったらおかしいということです。

3−3.項目の削除

Dictionaryから指定した項目を削除するにはRemoveメソッド を使用します。Removeメソッドで削除するという点はListと同じですが、引数には項目そのものではなくキーを指定します。

また、位置を指定して項目を削除するRemoveAtメソッドはDictionaryにはありません。Dictionaryはインデックスではなくキーで項目を識別しているため位置という考え方がないのです。

Clearメソッドですべての項目を削除できるのはListと同じです。

次の例はRemoveの使用例です。ポイントはRemoveの引数にインデックスの数値や、「足利尊氏」を指定するのではなく、キーである「室町幕府」を指定している点です。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim names As New Dictionary(Of String, String)

names.Add("江戸幕府", "徳川家康")
names.Add("室町幕府", "足利尊氏")
names.Add("鎌倉幕府", "源頼朝")
names.Add("大和朝廷", "神武天皇")

'キー「室町幕府」を使って、「足利尊氏」を削除
names.Remove("室町幕府")

'値の一覧を表示。(徳川家康、源頼朝、神武天皇)
MsgBox(String.Join(vbNewLine, names.Values.ToArray))

■リストC-8:

最後におまけでDictionaryに追加されている値の一覧を表示しています。DictionaryのValuesプロパティ (読み方:バリューズ)を使用すると値だけのコレクションを取得できますので、これを配列化してString.Joinを使って改行をはさんで結合しています。.NET Framework 4以上(VB2010以上)であれば、ToArrayがなくても動作します。

メモ メモ - ここにもコレクション Dictionary.Values

本文中では簡単にValuesプロパティを使って値だけのコレクションを取得できると書いていますが、このコレクションはListでもDictionaryでもなく ValuesCollection (読み方:バリューズコレクション)という特殊なコレクションです。

ListとDictionaryの知識があれば簡単に操作できますので特に勉強する必要はありません。項目の追加は親であるDictionaryに対して行ってください。

このように.NET Frameworkにはいろいろなところにさまざまなコレクションが使われていて、普段気にせず使っているものが実はコレクションであるということもあるかもしれません。

3−4.値を1つ取得

既に説明したようにDictionaryから値を1つ取得するには、Itemプロパティにキーを指定します。

Itemプロパティは省略可能なので、Dictionaryをあらわす変数名に直接かっこでキーを指定するような書き方になります。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim names As New Dictionary(Of String, String)

names.Add("江戸幕府", "徳川家康")
names.Add("室町幕府", "足利尊氏")
names.Add("鎌倉幕府", "源頼朝")
names.Add("大和朝廷", "神武天皇")

Dim edoStarter As String
edoStarter = names("江戸幕府")

'徳川家康
MsgBox(edoStarter)

■リストC-9:

発展 発展学習  -  Dictionaryの項目の位置

発展学習では意欲的な方のために現段階では特に理解する必要はない項目を解説します。

Dictionaryには位置という概念がないためインデックスを指定して項目を取り出すということはできないのですが、後述するFor Each構文を使ったり、ElementAtメソッドを使うことで「何番目の項目を取得する」という記述自体は可能です。

しかし、Dictionaryはそもそも追加された順番の記録を保証しておらず、For Eachで項目を処理する順番にも保証はないため、この「何番目」には意味がありません。

 

3−5.キーの操作

Dictionaryは同じキーで項目を追加することはできません。ContainsKeyメソッド (読み方:コンテインズキー)を使用すると、既にそのキーが含まれているか確認することができます。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim names As New Dictionary(Of String, String)

names.Add("江戸幕府", "徳川家康")
names.Add("室町幕府", "足利尊氏")
names.Add("鎌倉幕府", "源頼朝")
names.Add("大和朝廷", "神武天皇")

If names.ContainsKey("鎌倉幕府") Then
    MsgBox("鎌倉幕府は既にnamesに登録されています。")
End If

■リストC-10:

ただ、登録されていなければ登録したい、登録されていれば上書きしたいという場合はContainsKeyで判断するよりもItemプロパティを使ったほうがプログラムの行数は短くなります。

次の例は南朝がキーとして登録されていなければ項目を追加し、登録されていても上書きします。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim names As New Dictionary(Of String, String)

names.Add("江戸幕府", "徳川家康")
names.Add("室町幕府", "足利尊氏")
names.Add("鎌倉幕府", "源頼朝")
names.Add("大和朝廷", "神武天皇")
'names.Add("南朝", "劉裕") '←この行があってもなくても結果は同じ

names("南朝") = "後醍醐天皇"

■リストC-11:

処理の意味をはっきりさせたい場合ContainsKeyを使ってあえて切り分けて書くほうを好む人もいるかもしれません。

 

Dictionaryに格納されているキーはKeysプロパティ (読み方:キーズ)で取得できます。

なお、既に説明していますが、値の一覧はValuesプロパティで取得できます。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim names As New Dictionary(Of String, String)

names.Add("江戸幕府", "徳川家康")
names.Add("室町幕府", "足利尊氏")
names.Add("鎌倉幕府", "源頼朝")
names.Add("大和朝廷", "神武天皇")

'キーの一覧を表示 (江戸幕府、室町幕府、…)
MsgBox(String.Join(vbNewLine, names.Keys.ToArray))

'値の一覧を表示 (徳川家康、足利尊氏、…)
MsgBox(String.Join(vbNewLine, names.Values.ToArray))

■リストC-12:

4.StackとQueue

4−1.StackとQueueの概要

Stack(読み方:スタック)とQueue(読み方:キュー)は、値の出し入れの仕方に特徴があるコレクションです。

自分でStackとQueueを使うことはほとんどないと思いますが、StackとQueueの考え方はプログラマには常識となっているので、考え方だけは知っておきましょう。

使い方の方は実際に必要になったときに調べる程度で問題ないはずです。

4−2.Stack

Dictionaryが辞書なら、Stackは干草の山です。

Stackのイメージ

■画像:もっと山のように積んである干草の画像が欲しかったのですが…。

Stackに対して項目を追加するということは、山の上に干草を載せることを意味します。干草を取り出すには上から順番に取り出すことになります。

つまり、Stackは「後に追加したものを先に取り出す」というところに特徴があります。これをLIFO = Last In First Out = 後入れ先出し と呼びます。

Stackへの項目の追加にはPushメソッド(読み方:プッシュ)を、項目を取り出すにはPopメソッド(読み方:ポップ)を使用します。Popで取り出した項目はStackからは削除されます。

値を取り出さずに先頭の項目を取得するPeekメソッド(読み方:ピーク)もあります。

次の例はStackの使用例です。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim animals As New Stack(Of String)

animals.Push("アメンボ")
animals.Push("イノシシ")
animals.Push("ウマ")

Dim result1 As String = animals.Pop()
MsgBox(result1) 'ウマ

Dim result2 As String = animals.Pop()
MsgBox(result2) 'イノシシ

■リストD-1:

Stackは作業中の履歴のような管理に向いています。たとえば、VBではメソッドを次々と呼び出していくとき、メソッドの呼び出しが終了したら呼び出しもとのメソッドに帰っていく必要があります。だから、メソッドがどのように呼び出されているかはスタックの考え方で管理されています。

4−3.Queue

Stackが干草の山ならQueueは行列です。人気ラーメン屋の行列やお役所の窓口の行列のイメージです。こちらは、先に並んだ人から順番に処理されていきます。

つまり、Queueは「先に追加したものを先に取り出す」というところに特徴があります。これをFIFO = First In First Out = 先入れ先出し と呼びます。

Queueに項目を追加するにはEnqueueメソッド(読み方:エンキュー)を使用します。項目を取り出すにはDequeueメソッド(読み方:デキュー)を使用します。Dequeueで取り出した項目はQueueからは削除されます。

値を取り出さずに先頭の項目を取得するPeekメソッド(読み方:ピーク)がある点はStackと同じです。

次の例はQueueの使用例です。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim animals As New Queue(Of String)

animals.Enqueue("アメンボ")
animals.Enqueue("イノシシ")
animals.Enqueue("ウマ")

Dim result1 As String = animals.Dequeue
MsgBox(result1) 'アメンボ

Dim result2 As String = animals.Dequeue
MsgBox(result2) 'イノシシ

■リストD-2:

リアルタイムな処理が必要なシステムでは、処理の要求が発生したときに逐一処理を実行していくのではなく、一度キューに保存して、順番に処理を実行していくという手法を取ることがあります。こういう作りにしておくと処理を行う側は作業をたんたんとこなせばよく、要求を出す側は好きなタイミングで並列で要求を出すことができるというメリットがあります。

ただ、ほとんどの場合このような構造は自分でプログラムするものではないので、プログラマーはキューの意味さえわかっていればだいたい十分です。

発展 発展学習  -  Visual Basic コンパイラの特別仕様

発展学習では意欲的な方のために現段階では特に理解する必要はない項目を解説します。

既定のプロパティがないコレクションの場合、配列のように animals(1) という具合にかっこをつけて数字を指定すると、その位置にある項目を取得することができます。StackやQueueにも当てはまります。

このとき、実際には値を取り出すために後で説明するElementAtOrDefaultというメソッドが使用されます。

これはVisual Basicのコンパイラの特別な仕様で、どうも「IEnumerableインターフェースを実装するクラスが、既定のプロパティ(やインデクサ)を持たない場合、直接 ( ) をつけるとElementAtOrDefaultメソッドの呼び出すとみなす。」ということのようです。このことが正式にドキュメントに書いてあるのを見たことがありません。

VBのコンパイラの仕様なので、C#で同じように書くとエラーになります。もっとも、ElementAtOrDefaultメソッドを自分で呼び出す必要があるかないかだけの違いです。

5.その他のコレクション

List, Dictionary, Stack, Queue以外にもさまざまなコレクションがあります。その違いは項目を操作するアルゴリズムが特別だったり、並び順を常に維持している、複数の処理が並列に読み書きしたときにどうなるかなどです。

たとえば、LinkedList (読み方:リンクドリスト)を使うと、別の項目の前後やコレクションの先頭や後ろに新しい項目を追加でき、その処理の性能が良いです。項目を途中に挿入する処理が多い場合は選択肢になるでしょう。

英語ですが、こちらのページには主だったものの一覧表もありよくまとまっているようです。

http://geekswithblogs.net/BlackRabbitCoder/archive/2011/06/16/c.net-fundamentals-choosing-the-right-collection-class.aspx

機能的にはListとDictionaryが使えればほぼ問題ありません。性能的にもコレクションがネックになることは通常はないでしょう。何か特別に実現したいものがある場合に他にもっと適したコレクションがあるか調べてみるのが良いと思います。

それまではListとDictionary以外のことは、「何か特別なコレクションもあったなぁ」という具合に頭の片隅においておくくらいをお勧めします。

博士のワンポイントレッスン
V太 V太:博士〜。コレクションがいろいろあってわけがわかりません。List, Dictionary, Stack, Queue…。
B子 B子:V太は本当に整理するのが苦手ね。配列みたいに数値でアクセスしたいときはList、そうじゃなくって文字でアクセスしたい場合はDictionaryと覚えればいいじゃない!
V太 うーん。でも、なんかしっくりこないなぁ…。それに他にもLinkedListとかSortedDictionaryとかいろいろあるんでしょう?
博士 博士: まぁ混乱するのも無理はないな。「コレクション」という似たようなものがたくさんあるのに使い方がちょっとづつ違うのじゃからな。

まずはとにかくListを使ってみることじゃ。Listは実際に使ってみるととても簡単なんじゃよ。きっとすぐに使えるようになるじゃろう。

B子 ListになれたらDictionaryね。
博士 その後はヘルプを見ながら良さそうなコレクションを使っていくようにすれば自然といろいろなコレクションが使えるようになっていくじゃろう。
V太 はぁぁ。

 

メモ メモ - 古いコレクション ArrayList, Hashtableなど

先日(2015年12月)、新しく作成したシステムでArrayList (読み方:アレイリスト)というコレクションが使われているのを目撃しました。

ArrayListはVB.NET2002, 2003の時代に主流だったコレクションで、今で使われていません。

この時代には型パラメーターを定義できるジェネリックという機能がVBに導入されておらず、ArrayList (読み方:アレイリスト)はその時主流だったコレクションです。他にHashtable(読み方:ハッシュテーブル)というコレクションも良く使われていました。

今でもArrayListやHashtableがなくなったわけではないので使うことはできますが、メリットがないので使わないようにしましょう。

私が見たシステムのプログラマにもそのように伝えておきました。

6.項目の列挙 For Each

6−1.列挙の概要

項目の列挙はコレクションの花形です。VBやC#にはこのための専用の構文があります。

項目の列挙とはコレクションに追加されているすべての項目に対して順次処理を行っていくことを指します。

処理の内容はさまざまです。単に値を表示するだけの場合もあれば、データベースに登録するということもあるでしょう。もっと別の処理もあります。

この章で説明する内容はListやDictionary、Stack、Queueなどすべてのコレクションで共通です。

6−2.For Each 構文 の基本

コレクションに対してはFor Eachの構文(読み方:フォー イーチ)を使ったループを使用することができます。For Eachは配列とコレクション専用のループ方式です。初級講座では前回も含め既に何度かFor Eachが登場しましたがここであらためて説明します。

For Eachはコレクションの項目を1つずつ取り出して処理を行うループです。たとえば、次のコードでは、Listであるkanjisに4つの漢字を追加し、For Eachを使って1文字ずつ取り出してそれぞれの旧字体を取得し、リストボックスに表示します。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim kanjis As New List(Of String)

kanjis.Add("国")
kanjis.Add("学")
kanjis.Add("宝")
kanjis.Add("殴")

For Each kanji As String In kanjis
    Dim newType As String '新字体
    Dim oldType As String '旧字体

    newType = kanji
    oldType = StrConv(kanji, VbStrConv.TraditionalChinese)
    ListBox1.Items.Add(newType & " - " & oldType)
Next

■リストF-1:新旧漢字対応表

結果は次のようになります。

さて、For Eachの構文は次のようになっています。

(VB.NET 2002 対応) VB.NET 2003 対応 VB2005 対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

For Each @変数名 As A型 In Bコレクション

    'ここに処理を書く 

Next

Bコレクションにはループ処理する対象のコレクションを指定します。

For Eachはここで指定されたコレクションの項目数分処理を繰り返します。この例ではコレクションであるkanjisには4つの項目を追加していますから、このループは4周実行されます。

「4周」と言っても考え方としては、1周目、2周目、3周目、4周目ではなく、「国」の周、「学」の周、「宝」の周、「殴」の周というようにコレクションの各項目ごとに1周が割り当てられるというものです。

そのため、通常のFor〜Nextのループと違って、カウンター変数で今何周目か調べることはできません。その代わりに、@変数名 を使って、今何の周か「国」か「学」か「宝」か「殴」かを取得することができます。

この@の変数をカウンター変数とも呼びます。

上の例でFor Eachの部分を次のように書き換えると、単純に今何の周か表示を見ながら確認できます。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

For Each kanji As String In kanjis
    MsgBox(kanji & "の周")
Next

■リストF-2:今何の周?

A型は@変数名の型を指定するのですが、ここはコレクションの型パラメーターで指定した値の型にあわせておいてください。どうせ合わせるのであれば、この記述は無駄なので、VB2008以上ならば型推論機能にお任せすることにして「As A型」は書かなくても良いです。

その他 VB.NET 2002では、For Eachで変数を宣言できない。

本文では@変数名はFor Eachで宣言する変数になっていますが、Dimなどで事前に宣言しておいた変数を指定することもできます。この場合「As A型」は書くことができません。

ただし、VB.NET2002ではFor EachやForで変数を宣言することができないため、事前にDimで宣言しておくことが必須になります。だから「As A型」が登場することは絶対にありません。

逆にVB2008以上では、賢い型推論機能があり、For Eachの構文の場合@変数名の型はBコレクションの項目の型に決まっているので、「As A型」を書く必要がなくなります。こちらの場合は、見た目のわかりやすさや自動的なチェック機能と考えてあえて「As A型」を書くというのはありです。

ちなみにコレクションをループで処理するためのFor Eachの構文はVB4から導入されました。

 

メモ 参考  -  旧字体の取得

例として漢字の旧字体を取得するプログラムを紹介しましたが、全ての漢字でうまくいくわけではありません。このプログラムの実体は現代の漢字を中国語の簡体字にみたてて対応する繁体字を取得しているだけです。

私も中国語のことはよくわからないのですが、例で使った4つの漢字はうまい具合に日本の新字体=中国の簡体字、日本の旧字体=中国の繁体字となっている漢字のようです。この変換機能は内部ではWindowsの機能を使用しているためWindowsのバージョンによっては結果が異なる可能性があります。

6−3.DictionaryへのFor Each

For Eachはすべてのコレクションに有効なのですが、Dictionaryのように1つの項目にキーと値のような構造がある場合の処理のテクニック(といってもごく初歩的なもの)を紹介しておきます。

まず、前述しましたが、Dictionaryの場合、キーの型と値の型は型パラメーターで指定できますが、項目の型はKeyValuePair型です。だから、カウンター変数の型は常にKeyValuePair型になります。

ここから、キーと値を取り出すにはKeyプロパティ(読み方:キー)とValueプロパティ(読み方:バリュー)を使用します。

VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim names As New Dictionary(Of String, String)

names.Add("江戸幕府", "徳川家康")
names.Add("室町幕府", "足利尊氏")
names.Add("鎌倉幕府", "源頼朝")
names.Add("大和朝廷", "神武天皇")

For Each item In names
    Dim note As String = "{0}の開祖は{1}です。"
    note = String.Format(note, item.Key, item.Value)
    MsgBox(note)
Next

■リストF-3:VB2005の場合、itemにAs KeyValuePair(Of String, String)をつければ動作します。

はじめからキーの一覧にしか興味がない場合は、names.Keysに対してFor Eachループを使用するのが早いです。

VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

For Each key In names.Keys
    MsgBox(key)
Next

■リストF-4:

この場合でも、キーがわかるので、DictionaryのItemプロパティを使って値を取得することも可能です。

同様に、値の一覧にしか興味がない場合は、names.Valuesに対してFor Eachループを使用するのが早いです。

6−4.列挙中は値の変更ができない

For Each 〜 Nextでループしている間は、そのコレクションの値が変わるような操作はできません。AddやRemoveを行うとInvalidOperationExceptionの例外が発生します。

そのため、たとえば特定の条件に合う要素を削除するというプログラムを次のように書くことはできません。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

'この例はInvalidOperationExceptionの例外が発生します。

Dim areas As New List(Of String)

areas.Add("北海道")
areas.Add("千葉県")
areas.Add("東京都")
areas.Add("広島県")

For Each area As String In areas
    '「県」を削除する
    If area.EndsWith("県") Then
        areas.Remove(area)
    End If
Next

■リストF-5:列挙中に項目を削除して例外が発生する例

For Eachを使ってこの処理を行いたい場合は、面倒でも対象を別のコレクションに格納し、後で削除することになります。

次はその例です。

VB2005対応 VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim areas As New List(Of String)

areas.Add("北海道")
areas.Add("千葉県")
areas.Add("東京都")
areas.Add("広島県")

Dim deleteAreas As New List(Of String)

For Each area As String In areas
    '削除する「県」を記録する
    If area.EndsWith("県") Then
        deleteAreas.Add(area)
    End If
Next

For
Each area As String In deleteAreas
    '「県」を削除する
    areas.Remove(area)
Next

■リストF-6:

最初のFor Eachはareasのループなので、areasの変更はできませんが、他のコレクションの変更は可能です。ここで削除する項目をdeleteAreasという別のコレクションに追加しています。

2つ目のFor EachはdeleteAreasのループなので、areasを変更することができます。

なお、この例の機能であれば、For Eachを使わずに次のように書くと楽です。

VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim areas As New List(Of String)

areas.Add("北海道")
areas.Add("千葉県")
areas.Add("東京都")
areas.Add("広島県")

areas.RemoveAll(Function(area) area.EndsWith("県"))

■リストF-7:ラムダ式を使って条件に合致しない項目を削除する

これはVB2008から導入されたラムダ式という機能を使っている例で、初級講座の現段階ではまだラムダ式を説明していないので詳細は割愛しますが、ラムダ式やLINQという技術はコレクションと非常に相性がよく、コレクションのプログラムを簡略化してくれます。

7.さまざまな便利機能

7−1.便利機能

項目の追加や削除の方法がわかって、For Eachで列挙させることができれば、あとはプログラム次第で何でもできるようになります。

しかし、よくありそうな処理は簡単に利用できるようになっており知っていると便利です。ここではそういった処理をざっと紹介します。

この章で紹介する機能もコレクションであれば共通で使用できる機能ですが、.NET FRamework 3.5でLINQという機能が導入されたときにあわせて導入された機能なので、利用するにはVB2008以上が必要です。

ここで紹介する以外にも便利機能があるのですが、初級講座の現段階では使い方が難しいので割愛します。

7−2.合計・平均・個数・最大値・最小値

合計・平均・個数・最大値・最小値は自分で計算しなくても下記メソッドを使って簡単に求められます。

  メソッド 読み方
合計 Sum サム
平均 Average アベレージ
個数 Count カウント
最大値 Max マックス
最小値 Min ミン

合計を求める例

VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim points As New List(Of Integer)
points.Add(10)
points.Add(5)
points.Add(8)

'合計を取得。23になる。
Dim result As Integer
result = points.Sum

MsgBox(result.ToString)

■リストG-1:

7−3.合成 (和集合・差集合・積集合)

2つのコレクションを合体させたり、2つのコレクションの中で一致するものだけを抜き出したり、差があるものだけを抜き出すことができます。

  メソッド 読み方  
合体 Union ユニオン 2つのコレクションの和集合を返します。
一致しないものを抽出 Except エクセプト 片方のコレクションに含まれていないものを返します。
一致するものを抽出 Intersect インターセクト 2つのコレクションの積集合を返します。

片方のコレクションに含まれないものだけを抽出する例

VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim list1 As New Dictionary(Of String, String)
list1.Add("あ", "あめんぼ")
list1.Add("い", "いのしし")
list1.Add("う", "うま")

Dim list2 As New Dictionary(Of String, String)
list2.Add("あ", "あめんぼ")
list2.Add("か", "からす")
list2.Add("う", "うぐいす")

'list1の中でlist2に含まれていないものだけを取得
Dim results = list1.Except(list2)

For Each result In results
    MsgBox(result.ToString)
Next

■リストG-2:

7−4.逆転・重複排除

コレクションの順番を逆転させたり、重複する項目を排除したりできます。

  メソッド 読み方  
逆転 Reverse リバース コレクションの並び順を逆転させます。
重複排除 Distinct ディスティンクト 重複しているものを排除したコレクションを作成します。

7−5.先頭・最後・位置指定

先頭の項目、最後の項目を取得できます。項目の位置を指定した取得もできますが、位置指定の場合、コレクションにそもそも位置という概念がない場合、状況によって取得できる項目が変わる場合があります。

また、対象の項目がない場合、その型の既定値(Integerなら0、StringならNothingといったもの)を取得する機能もあります。

  メソッド 読み方  
先頭 First ファースト  
先頭または既定値 FirstOrDefault ファーストオアデフォルト 項目がない場合、その型の既定値を返します。
最後 Last ラスト  
最後または既定値 LastOrDefault ラストオアデフォルト 項目がない場合、その型の既定値を返します。
指定位置 ElementAt エレメントアット  
指定位置または既定値 ElementAtOrDefault エレメントアットオアデフォルト 指定位置に項目がない場合、その型の既定値を返します。

7−6.型によるフィルター

OfTypeメソッド(読み方:オブタイプ)コレクションの項目の中で特定の型の項目だけを抽出することができます。

これを使うとフォーム上のコントロールのうち、TextBoxだけの一覧を取得するなどの処理がとても簡単になり私は重宝しています。

  メソッド 読み方  
型によるフィルター OfType オブタイプ 指定した型の項目だけを抽出します。

OfTypeメソッドは対象の型を指定するので、引数は型パラメーターです。つまり、引数の前にOfが必要であるということです。

フォームに直接貼り付いているTextBoxの一覧を取得し、すべてのTextBoxを使用不可にする例を紹介します。

この例ではPanel内部に貼り付いているなど、フォームに直接貼りついていないTextBoxは取得できません。FormのControlsプロパティが直接の子コントロールのコレクションだからです。

VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応

Dim textBoxes = Me.Controls.OfType(Of TextBox)

For Each textBox In textBoxes
    textBox.Enabled = False
Next

■リストG-3:

7−7.Listへの変換・配列への変換

Listや配列は基本的で汎用的なので変換したくなることがありますが、それも簡単にできます。

古いメソッドやプロパティには配列を設定しなければいけないものもあるのですが、そんなときも安心です。

  メソッド 読み方
配列への変換 ToArray トゥーアレイ
Listへの変換 ToList トゥーリスト