Visual Basic 初級講座 [改訂版]
VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

第16回 コレクション1

2020/7/12

この記事が対象とする製品・バージョン

VB2019 Visual Basic 2019 対象です。
VB2017 Visual Basic 2017 対象です。
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 対象外ですがほとんどの説明があてはまるので参考になります。
VB.NET 2002 Visual Basic.NET (2002) 対象外ですがほとんどの説明があてはまるので参考になります。
VB6対応 Visual Basic 6.0 × 対象外です。

 

目次

 

 

1.コレクションの概要

1-1.簡単な例

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

コレクションにはList、Dictionary、Stack, Queueなどいろいろな種類があります。

 

下記はコレクションを使った簡単なプログラムの例です。

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

Dim countries As New List(Of String)

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

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

■リストA-1:コレクションの簡単な例 → Debug.WriteLineが表示される場所

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

 

1-2.配列との違い

コレクションは配列と良く似ていますが、配列では扱いが面倒だった点が改善されておりとても使いやすくなっています。

リストA-1と同じことを配列で書いてみると次のようになります。

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

Dim countries(2) As String

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

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

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

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

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

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

 

表にまとめおきます。(この表には後で説明するな要も含んでいます。)

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

  配列 コレクション(Listの場合)
要素の数 使う前に決める
Dim names(2) As String
(何もする必要なし)
要素数の拡張 Redim Preserve names(100) (何もする必要なし)
要素の型 宣言時に指定する
Dim names(2) As String
宣言時に指定する
Dim names As List(Of String)
項目の追加 インデックスを指定して追加する
names(0) = "A"
names(1) = "B"
Addメソッドを使用する。
names.Add("A")
names.Add("B")
項目の取得 name = names(1) name = names(1)

 

 

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

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

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

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

コレクションの使用割合

 

2.List

2-1.Listの概要

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

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

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

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


Dim
myList As New List(Of String)

■リストB-1

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

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


Dim
myList As New List(Of Integer)

■リストB-2

このようなに型を引数のように指定する機能を「ジェネリック」と呼び、Ofに続いて指定される型を「型パラメーター」と呼びます。型パラメーターを指定する場合は必ずOf を使用します。

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

 

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

VB2010以上ではList型の変数を宣言するときに初期値を設定することができます。

これには From (読み方:フロム)を使った「コレクション初期化子」という特別な機能を構文を使用します。

VB2010対応 VB2012対応 VB2013対応 VB2015対応 VB2017 VB2019


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

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

この例では、初期値として「アラビラ」「インド」「ウズベキスタン」の3項目をもったListを作成します。

 

2-3.項目の追加

2-3-1.Addメソッド (項目を1つ追加)

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

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

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

Dim stars As New List(Of String)

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

'Listの内容を表示
Debug.WriteLine(String.Join(Environment.NewLine, stars.ToArray))

■リストB-5:→ Debug.WriteLineが表示される場所

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

メモ メモ  -  String.Join(Environment.NewLine, stars.ToAarray) にこめられた含蓄

この例では おまけとして、最後にListの内容を表示するようにしてみました。この最後の行で使っているStringクラスのJoinメソッド(読み方:Join=ジョイン)は、配列の各要素を1つの文字列として結合する機能です。結合するときに各要素の間に挟む文字を第1引数で指定できます。この例では改行(Environment.NewLine)をはさんで結合するようにしています。

VB2008以前では、Joinメソッドは、コレクションではなく「配列」しか対象にしてくれないため、ToArrayメソッド(読み方:ToArray = トゥーアレイ)を使ってコレクションを配列に変換しています。このようにToArrayメソッドを使うだけでコレクションを配列に変換できるので、配列用の便利機能も簡単に利用できます。VB2010以上(.NET Framework 4以上)であれば、JoinメソッドはListを対象にできるように機能が拡張されているので、ToArrayは不要です。

 

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

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

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

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の共通の基底クラスであるControl (読み方:Control = コントロール)を指定します。

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

Dim controlList As New List(Of Control)

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

■リストB-7:

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

博士のワンポイントレッスン 「基底クラス・派生クラス」
V太 V太:博士~。「基底クラス」とか「派生」ってなんでしたっけ?
博士 博士:う~ん。まだちゃんと説明しとらんかったのぉ。クラスを継承するときに、元となるクラスを基底クラス、子どもとなるほうのクラスを派生クラスと呼ぶのじゃ。ろくに説明しとらん状態でわかるかのぉ?
B子 B子:簡単に言うとね、.NETではすべてのクラスが階層化されていて親子関係があるのよ。親のことを「基底クラス」、子供のことを「派生クラス」というのよ。親をどんどんたどっていくとどのクラスもObjectに行き着くの。
博士 博士:ここでは「階層化」と言っているが、正式には「継承」という機能なのじゃ。

 

 

2-3-2.AddRangeメソッド (項目を複数追加)

AddRangeメソッドを使うと複数の項目を一度に追加することができます。

引数には配列またはコレクションを指定します。

どこかで既に作成済みの配列やコレクションの内容をそのままListに取り込みたい場合に便利です。

 

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

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

Dim stars As New List(Of String)

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

■リストB-8:

次の例ではList自体を引数にして項目を追加しています。

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

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:

 

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

Insertメソッド(読み方:Insert = インサート)を使うと、位置を指定して項目を挿入できます。

次の例では、先頭(インデックスが0の場所)に「地球」を挿入します。

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

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

Debug.WriteLine(stars(0)) '火星と表示されます。

stars.Insert(0, "地球") '先頭に「地球」を挿入
Debug.WriteLine(stars(0)) '地球と表示されます。

指定した位置に複数の項目を挿入するInsertRangeメソッド(読み方:インサートレンジ)もあります。

 

メモ メモ  -  挿入と削除の性能

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

LinkedList(読み方:リンクドリスト)というコレクションは、挿入と削除の性能が良いように作られているので、大量の項目に対し、項目の挿入や削除が頻繁にある場合は、使用を検討してみてください。

 

2-4.項目の削除

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

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

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

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

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

Dim stars As New List(Of String)

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

stars.Remove("火星")

'Listの内容を表示
Debug.WriteLine(String.Join(Environment.NewLine, stars.ToArray))

■リストB-10:

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

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

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

Dim stars As New List(Of String)

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

stars.RemoveAt(2)

'Listの内容を表示
Debug.WriteLine(String.Join(Environment.NewLine, stars.ToArray))

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

 

2-5.項目を1つ取得

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

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

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

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

Dim stars As New List(Of String)

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

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

Debug.WriteLine(myStar)

■リストB-12:

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

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

Dim stars As New List(Of String)

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

Dim myStar As String
myStar = stars(1)

Debug.WriteLine(myStar)

■リストB-13:

 

2-6.練習問題

当てはまるのが、配列かListか、その両方かを回答してください。

問1.使用する前に要素の数を決める必要がない。
配列
List
配列とリスト
問2.添字(インデックス)で要素を取得できる。
配列
List
配列とリスト
問3.Addメソッドで簡単に要素を追加できる。
配列
List
配列とリスト
問4.■に当てはまるものは何でしょう?

Dim
stars As New List(■ String)
Of
From
With

 

 

雑談 雑談  -  Of

アメリカのMicrosoftのVisual Basic開発チームの技術者に聞いた話です。Visual Baisc 2005でジェネリックが導入されたときにどういう記号やキーワードを割り当てるのが良いかMicrosoft社内で検討されたということです。

長いBASIC言語の歴史の中でもジェネリックの導入ははじめてのことです。

Dim x As List[String]、Dim x As List(String)、Dim x As List(Of String)など、具体的にどういう候補があったのかは忘れましたが、Visual Basic の雰囲気に一番合っているのはどういう書き方か検討した結果 (Of 型名) が採用されたそうです。 List(Of String) は (Stringの)List という風に読めるので、確かに、なんとなくプログラムを読み下せるBASIC言語にマッチしているように思います。

その頃、C#チームは List<string> という < > を使った書き方を決めていました。これは多分ジェネリックに似たC++言語のテンプレートという機能の書き方を真似たもので、C#チームが新しく考えた書き方ではないと思います。

ところで、その後.NET Frameworkに OfType というメソッドが追加され、このメソッドはジェネリックの引数(つまり型パラメーター)を取ります。これが、VBのOfと重なって、Dim items = controls.OfType(Of TextBox)のようなプログラムが出現することになりました。是非はともかくこの、OfType(Of ...) というつながりが何か使うたびに印象的に感じられます。

 

 

 

3.Dictionary

3-1.Dictionaryの概要

Dictionaryは Listの次のよく使うコレクションです。

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

この見出しのことをキーと呼びます。同じキーの項目を複数追加することはできません。

辞書の写真

■画像:Dictionaryのイメージ

Dictionaryの各項目はそれぞれがキー と 値を持っているので、項目を追加するときにキーと値の2つを指定する必要があります。

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

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

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

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


Dim myDic As New Dictionary(Of String, String)

■リストC-1:

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

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


Dim myDic As New Dictionary(Of Date, Image)

■リストC-2:

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

 

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

VB2010以上では、Dictionaryの宣言時に「コレクション初期化子」を使って初期値と設定することができます。

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

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

例を示します。

VB2010対応 VB2012対応 VB2013対応 VB2015対応 VB2017 VB2019

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-3.項目の追加

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

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

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

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

Dim names As New Dictionary(Of String, String)

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

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

■リストC-4:項目追加の例 → Debug.WriteLineが表示される場所

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

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

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

 

Itemプロパティを下記のように使って項目を追加することもできます。

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

Dim names As New Dictionary(Of String, String)

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

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

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

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

Dim names As New Dictionary(Of String, String)

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

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

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

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

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

Dim names As New Dictionary(Of String, String)

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

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

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

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

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

 

3-4.項目の削除

Dictionaryから指定した項目を削除するにはRemoveメソッド を使用します。引数には削除対象のキーを指定します。

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

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

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

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

Dim names As New Dictionary(Of String, String)

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

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

'値の一覧を表示。(徳川家康、源頼朝、神武天皇)
Debug.WriteLine(String.Join(Environment.NewLine, 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-5.値を1つ取得

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

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

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

Dim names As New Dictionary(Of String, String)

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

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

'徳川家康
Debug.WriteLine(edoStarter)

■リストC-9:

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

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

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

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

 

3-6.キーの操作

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

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

Dim names As New Dictionary(Of String, String)

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

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

■リストC-10:

「登録されていなければ新規登録したい、登録されていれば上書きしたい」という場合はContainsKeyを使わないでItemプロパティを使ってプログラムする方が楽です。

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

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

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対応 VB2017 VB2019

Dim names As New Dictionary(Of String, String)

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

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

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

 ■リストC-12:

 

4.演習 拡張子の統計

4-1.フォルダー選択

Dictionaryを使って、拡張子ごとのファイル数を数えるプログラムを作ってみましょう。

Windowsフォームアプリケーションを作成して、プロジェクト名は ExtentionCounter としてください。

 

フォームにはボタン(Button)とリストボックス(ListBox)を貼り付けて、プロパティは下記の通りにしてください。

コントロール プロパティ 備考
フォーム Text 拡張子の統計  
Button (Name) btnCount  
  Text フォルダー選択  
ListBox (Name) lstResult  
  Font メイリオ、サイズ 12  
  Anchor Top,Bottom,Left,Right フォームの大きさを変更するとListBoxの大きさも変わるようになります。

配置やその他のデザインはお好みでアレンジしてください。私は次のように配置しました。

コントロールの配置

 

 

ボタンをクリックしたら、ファイルが格納されているフォルダーをユーザーに選択させます。次のようなプログラムになります。

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

Private Sub btnCount_Click(sender As Object, e As EventArgs) Handles btnCount.Click

    Dim
dialog As New FolderBrowserDialog

    dialog.Description = "拡張子を数えるフォルダーを選択してください。"

    'ここでダイアログが表示されます。ユーザーが選択を完了するまでプログラムはこれ以上進みません。
    Dim result As DialogResult = dialog.ShowDialog

    If result = DialogResult.Cancel Then
        'ユーザーがキャンセルを選択した場合何もしません。
        Return
    End If

    'ユーザーが選択したフォルダー名のパスを表示します。
    MsgBox(dialog.SelectedPath,, "選択されたフォルダーのパス")

End Sub

FolderBrowserDialogクラス(読み方:FolderBrowserDialog = フォルダーブラウザーダイアログ)はフォルダーを選択させるときにとても便利です。子画面を表示してユーザーにフォルダーを選択させる機能です。

FolderBrowserDialog

ShowDialogメソッド(読み方:ShowDialog=ショウダイアログ)は、画面を表示させ、ユーザーがキャンセルしたのか、OKしたのかを戻り値で返します。ユーザーが選択するまでプログラムはこれ以上進みません。

戻り値は DialogResult列挙体(読み方:DialogResult = ダイアログリザルト)です。上記のサンプルのように If 文でキャンセルされたか判断できます。この例ではキャンセルされた場合 Return (読み方:Return = リターン)を実行します。Returnを実行するEnd Subまで到達したのと同じ効果があり、要するにこの例ではプログラムはこれ以上実行されません。

ユーザーが選択したフォルダーのパスは SelectedPathプロパティ (読み方:SelectedPath = セレクテッドパス)で取得できます。

 

4-2.ファイル一覧の取得と表示

次に選択したフォルダ内のファイル一覧を取得してみます。繰り返しになるので前に示した部分のプログラムの大部分は省略します。最後にプログラム全体を載せます。

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

Private Sub btnCount_Click(sender As Object, e As EventArgs) Handles btnCount.Click


    …省略・・・

    'ユーザーが選択したフォルダー名のパスを表示します。
    MsgBox(dialog.SelectedPath,, "選択されたフォルダーのパス")

    'フォルダー内のファイルのパスの一覧を取得
    Dim fileFullPaths As String() = IO.Directory.GetFiles(dialog.SelectedPath)

    lstResult.Items.Clear()

    'ファイル名を拡張子の一覧を表示
    For Each fileFullPath As String In fileFullPaths
        Dim fileName As String = IO.Path.GetFileName(fileFullPath) 'ファイル名を取得
        Dim extension As String = IO.Path.GetExtension(fileFullPath) '拡張子を取得
        lstResult.Items.Add(fileName & " - " & extension)
    Next

End Sub

DirectoryクラスのGetFilesメソッド(読み方:Directory = ディレクトリ、GetFiles = ゲットファイルズ)は、フォルダー内のファイルの一覧を簡単に取得できる便利なメソッドです。戻り値は文字列型の配列で、フォルダー内のファイルのフルパスが格納されています。

とりあえず、ここでは確認のためこれをリストボックスに表示するプログラムにしています。2回目に実行してもおかしくならないよう、Items.Clearを使って表示前にリストボックスの内容をクリアします。

配列の内容はFor Eachのループで取り出します。ループ内でフルパスからファイル名と拡張子を取得して、Items.Add でリストボックスに追加します。

フルパスから情報を取得するには、Pathクラス(読み方:Path=パス)が便利で、このサンプルのようにファイル名・拡張子は一撃で取得できます。

この状態で実行すると次のように表示されます。

ファイルの一覧を表示

 

4-3.拡張子の統計

さて、単純にファイル名と拡張子を表示するのではなく、拡張子ごとのファイル数を表示するにはこれをどう改造したらよいでしょうか?

できあがりからイメージすると、次のように表示したいわけです。

拡張子の統計

この画面を見るとイメージが湧いてくるかもしれません。このデータの構造は、拡張子をキー、カウントを値とする辞書、つまりDictionaryで表現できるのです。

ですから、プログラム内で情報を格納するとために Dictionary(Of String, Integer) 型の変数を宣言して、ファイルの拡張子を調べて集計していく形になります。

はじめての人には難しいのですぐにこの下の正解を見るのが良いでしょう。他の言語で似たようなプログラムを作ったことのある経験者なら少し自分でチャレンジしてみても良いかもしれません。

 

正解は次のようになります。

拡張子とカウントを格納する辞書は Dim extensions As New Dictionary(Of String, Integer) と宣言しています。

VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応 VB2017 VB2019

Private Sub btnCount_Click(sender As Object, e As EventArgs) Handles btnCount.Click


    Dim dialog As New FolderBrowserDialog

    dialog.Description = "拡張子を数えるフォルダーを選択してください。"

    'ここでダイアログが表示されます。ユーザーが選択を完了するまでプログラムはこれ以上進みません。
    Dim result As DialogResult = dialog.ShowDialog

    If result = DialogResult.Cancel Then
        'ユーザーがキャンセルを選択した場合何もしません。
        Return
    End If

    'ユーザーが選択したフォルダー名のパスを表示します。
    MsgBox(dialog.SelectedPath,, "選択されたフォルダーのパス")

    'フォルダー内のファイルのパスの一覧を取得
    Dim fileFullPaths As String() = IO.Directory.GetFiles(dialog.SelectedPath)

    lstResult.Items.Clear()

    '拡張子(文字列)をキーに、カウント(数値)を格納するコレクション
    Dim extensions As New Dictionary(Of String, Integer)

    '拡張子を集計
    For Each fileFullPath As String In fileFullPaths
        Dim extension As String = IO.Path.GetExtension(fileFullPath) '拡張子を取得

        If extensions.ContainsKey(extension) = False Then
            'まだこの拡張子が extensions に追加されていなければ カウント 1 で追加する。
            extensions.Add(extension, 1)
        Else
            '既にこの拡張子が extensions 二登録されている場合は、値に1をプラスする。
            extensions(extension) += 1
        End If
    Next

    '拡張子とカウントを表示
    For Each extensionSummary In extensions
        Dim extension As String = extensionSummary.Key
        Dim count As Integer = extensionSummary.Value

        lstResult.Items.Add(extension & " - " & count & " ファイル")
    Next

End Sub

最大のポイントはコメントで「拡張子を集計」と書いてあるところのFor Each ~ Nextです。

ここでファイルのパスを1個ずつ調べて、拡張子を取り出し、ContainsKeyメソッドを使ってその拡張子が既にextensionsのキーとして登録されているかを調べます。

登録されていなければAddメソッドを使って、その拡張子をexntensionsに登録します。登録されていれば、カウントを +1 します。

このようにすることで拡張子とカウントを格納した辞書(Dictionary)が完成するので、最後にもう1度For Each ~ Nextでループを行ってこれをリストボックスに表示します。このようにコレクションに対しても配列と同じようにFor Eachを使用することができます。Dictionaryの場合、各項目はキーと値を格納するオブジェクトであり、これはKeyValuePair構造体 (読み方:KeyValuePair = キーバリューペア)です。この例の 変数extensionSummaryがこの KeyValuePair型です。KeyValuePairからキーと値を取り出すにはそれぞれ KeyプロパティとValueプロパティを使用します。この例の場合だとキーには拡張子が格納されており、値にはファイル数が格納されています。

 

4-4.LINQ

以上で説明したプログラムは、はじめてDictionaryを使ってプログラムする説明用にわかりやすさを優先して少し冗長に書いています。

もっと短く書く例を紹介します。この例では Dictionary は登場しません。

VB2008対応 VB2010対応 VB2012対応 VB2013対応 VB2015対応 VB2017 VB2019

Private Sub btnCount_Click(sender As Object, e As EventArgs) Handles btnCount.Click

    Dim dialog As New FolderBrowserDialog

    dialog.Description = "拡張子を数えるフォルダーを選択してください。"

    'ここでダイアログが表示されます。ユーザーが選択を完了するまでプログラムはこれ以上進みません。
    If dialog.ShowDialog = DialogResult.Cancel Then
        'ユーザーがキャンセルを選択した場合何もしません。
        Return
    End If

    'ユーザーが選択したフォルダー名のパスを表示します。
    MsgBox(dialog.SelectedPath,, "選択されたフォルダーのパス")

    'フォルダー内のファイル一覧を取得して拡張子別にカウントを集計
    Dim extensions = From fileFullPath In IO.Directory.GetFiles(dialog.SelectedPath)
                     Group By extension = IO.Path.GetExtension(fileFullPath)
                     Into Count
                     Select extension & " - " & Count & " ファイル"

    '拡張子とカウントを表示
    lstResult.Items.Clear()
    lstResult.Items.AddRange(extensions.ToArray)

End Sub

この例のポイントは集計を実行している From fileFullPath In ・・・ の部分です。この部分は LINQ (リンク) という特別な構文を利用しています。

LINQを使うと配列やコレクションをさまざまに集計したり、操作することができます。今回はLINQの説明は割愛しますが、LINQはコレクションととても相性がよく、便利に使えるようにデザインされているので、いろいろなデータを集計・操作する必要がある場合は、参考にしてみてください。

Visual Basic の LINQ の概要

https://docs.microsoft.com/ja-jp/dotnet/visual-basic/programming-guide/concepts/linq/getting-started-with-linq

 

データベースを操作する場合は、データベース製品自体に SQL というデータ操作用の言語が用意されており、LINQよりもこのSQLを使って操作するか、データベース専用のフレームワークやライブラリを使って操作するほうが一般的です。