Visual Basic 初級講座 [改訂版] |
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
Visual Basic 中学校 > 初級講座[改訂版] >
2021/9/26
この記事が対象とする製品・バージョン
![]() |
Visual Studio 2022 | ◎ | 対象です。 |
![]() |
Visual Basic 2019 | ◎ | 対象です。 |
![]() |
Visual Basic 2017 | ◎ | 対象です。 |
![]() |
Visual Basic 2015 | ◎ | 対象です。 |
![]() |
Visual Basic 2013 | ◎ | 対象です。 |
![]() |
Visual Basic 2012 | ◎ | 対象です。 |
![]() |
Visual Basic 2010 | ◎ | 対象です。 |
![]() |
Visual Basic 2008 | ◎ | 対象です。 |
![]() |
Visual Basic 2005 | ○ | 対象ですが、一部対応していない機能も扱います。 |
![]() |
Visual Basic.NET 2003 | × | 対象外です。 |
![]() |
Visual Basic.NET (2002) | × | 対象外です。 |
![]() |
Visual Basic 6.0 | × | 対象外です。 |
この記事は 後から追加したため、第20-3回という、中途半端な名称になってしまいました。
目次
ListやStackなどのコレクションでは、単にそれが配列であるとか、Listであるとか、Dictionaryであるという以外に、StringのListであるとか、IntegerのStackであるとか、LongのQueueであるというように、「○○型の△△である」という表現が適切な場合があります。
コレクションのところで説明したようにVBではこれを Of を使って表現できます。
たとえば、次の例はStringのListの変数を定義します。
Dim items As New List(Of String)
これはList(Of String)が型です。つまり、Integer型や String型があるのと同じように List(Of Integer)型や、List(Of String)型があります。このような型をジェネリック型と呼びます。
Of で指定されてている型は「型パラメーター」と呼びます。たとえば、List(Of String)の型パラメーターがStringです。型パラメーターは複数する場合もあります。たとえば、DictionaryではDictionary(Of String, Integer)のように2つの型パラメーターを採りますし、もっとたくさんの型パラメーターを指定するものもあります。
ジェネリック型の型の扱いが簡単になります。たとえば、List(Of Integer)とIntegerを使って次のようなプログラムを記述できますが、ここでは型変換など型の処理は一切必要ありません。
Dim items As New List(Of Integer)
items.Add(3)
items.Add(5)
Dim value As Integer = items(0)
Debug.WriteLine(value) ' 3 と出力されます。
Of String を使用すると、同じような処理を文字列型で記述することもできます。
Dim items As New List(Of String)
items.Add("Apple")
items.Add("Banana")
Dim value As String = items(0)
Debug.WriteLine(value) ' Apple と出力されます。
これがジェネリック型の効果であることを実感するために、ジェネリックがなかったVisual Basic.NET 2002, 2003 の時代に同じプログラムをどのように書いていたか見てみましょう。この時代にはListがそもそもなく、これに相当するのは ArrayList(読み方:ArrayListアレイリスト)というクラスです。
ArrayListは2021年現在では出番はありませんが、Visual Studio 2022でも実行することはできます。
'(Of Integer)のように型を指定できない。
Dim items As New ArrayList
'型が不明なので、値はObject型で定義されており、とりあえずなんでも値をセットできる。
items.Add(3)
items.Add(5)
'itemsが Integer であることが保証されていないので型変換が必要。
Dim value As Integer = CInt(items(0))
Debug.WriteLine(value) ' 3 と出力されます。
ポイントはコメントでも記載していますが、項目の型が不明な点です。ArrayListではどのような型でも代入できるように項目はObject型で定義されています。ListにたとえるとList(Of Object)という形です。そのため、具体的なIntegerやStringなどとして扱おうとすると型変換が必要になります。
この例では代入するときに一か所型変換するだけですが、集計したり、メソッドやプロパティを呼び出したり型を前提としたさまざまな処理をするときに型変換が必要になります。
Option Strict Off
にするとこの手の面倒から解放されますが、型が変換できることの責任はすべてプログラマーが負うことになり、型のメリットを享受できません。なにか型を前提とした処理をするときにその処理が成功することはプログラマーが確認する必要があります。Visual
StudioやVisual Basicのエラーチェックや入力補完などの便利機能の支援も受けられなくなります。
もっとも、その前のVB6自体ではそれが常識だったので、VB.NET2002, 2003の時代ではあまり違和感がなかったようにも思います。 |
Visual Basic では List(Of String) のような構文でジェネリック型を表現しますが、C# では List<String> のように記述します。
このことから、インターネット上のドキュメントやリファレンスではList(Of String)型のことを List<String> と表現する場合があるので覚えておいてください。
たとえば、Visual Basic のListクラスのリファレンスは2021年9月現在タイトルが「List<T> クラス」となっています。
List<T> クラス (System.Collections.Generic) | Microsoft Docs
このURL自体は各言語共有で、ページ左上の言語設定を切り替えるとVBやC#などの言語を切り替えられます。VBを選択してもタイトルは List<T>です。Visual Basicのリファレンスなのだから、Visual Basicの構文に合わせたタイトルにしてほしいものですが…。
また、List<String> でも List<Integer> でも、 その他のList<xxxxx> でも当てはまることを表現する場合、慣用的に T という文字を使って、List<T> と表現します。VBに配慮してList(Of T)と表現される場合もあります。
Dictionaryのように複数の型パラメーターをとるものは Dictionary<T1, T2> のように記述されることもありますし、1つ目の型パラメーターがキーの型、2つ目の型パラメーターが値の型であることから Dictionary<TKey, TValue>のように意味を表現する記述担っている場合もあります。
ジェネリック型の代入はルールが難しく混乱することがしばしばです。
簡単にポイントを説明しますが、先にアドバイスを書いておくと、あまりルールにこだわらすとりあえずプログラムしてみて、ルールに違反しているかの判断は、Visual Studioのエラーや警告表示に任せるというスタンスです。
最初に指摘しておきしておきたいのは、型パラメータが異なるList型間では代入ができないということです。
Dim itemsA As New List(Of Integer)({1, 2, 3})
'この代入はできません。ビルドエラーになります。
Dim itemsB As List(Of Long) = itemsA
このプログラムは、やろうとしている意図が明確であり、実行できてもいいように思えますがVBの言語仕様では許可されません。このような合理的でないエラーがなくなるようにVBは進化してきましたが、このプログラムは数少ない不合理な例です。
ジェネリックでなければ同じ意味の代入は可能です。
たとえば、次のプログラムは何の問題もないことから考えても、上記のジェネリックの場合に失敗するというのは意表をつかれます。とにかくできないということを覚えておきましょう。
Dim numberA As Integer = 627
'この代入はOK
Dim numberB As Long = numberA
一方、型パラメーター同士に継承関係がある場合は、代入に成功する場合があります。残念ながら常に成功するとは言えません。
次のIEnumerableという型を使ったジェネリック型では成功します。(これはVB2010からできるようになりました。VB2008以前では実行時に失敗します。)
Dim itemsA As IEnumerable(Of IO.FileInfo)
'この代入はOK
Dim itemsB As IEnumerable(Of IO.FileSystemInfo) = itemsA
この例では型パラメーターが異なる代入が成功していることに注目してください。FileInfoクラスはFileSystemInfoクラスの親クラスです。IEnumerableはこのような代入が許可されるように定義されており、この性質を「共変性」(きょうへんせい)と呼びます。
IEnumerableはコレクションのような集合を扱うのにとても便利なものでインターフェースという種類のものです。インターフェースについては初級講座第28ー2回、IEnumerableについては第36回で扱います。
これをListでやるとエラーになります。
Dim itemsA As List(Of IO.FileInfo)
'この代入はエラー
Dim itemsB As List(Of IO.FileSystemInfo) = itemsA
このときVBは次のエラーメッセージを出力します。(バージョンによって違うかもしれません。私はVisual Studio 2022 + .NET 6で試しています。)
エラー BC36756 'List(Of FileInfo)' を 'List(Of FileSystemInfo)' に変換できません。'IEnumerable(Of FileSystemInfo)' の使用を検討してください。
Listだとダメだから、IEnumerableを使いましょうというメッセージですね。
IEnumerableの説明は後日の予定ですが、どういうことかサンプルだけ紹介しておきます。次の例は成功します。
Dim itemsA As New List(Of IO.FileInfo)
itemsA.Add(New IO.FileInfo("C:\temp\test.txt"))
itemsA.Add(New IO.FileInfo("C:\temp\index.html"))
'この代入はOK
Dim itemsB As IEnumerable(Of IO.FileSystemInfo) = itemsA
'IEnumerableのままコレクションを操作するプログラムをすることもできます。
Dim file1 As IO.FileSystemInfo = itemsB(0)
Dim file2 As IO.FileSystemInfo = itemsB(1)
'ToListを使ってListに変換することもできます。
Dim itemsC As List(Of IO.FileSystemInfo) = itemsB.ToList
IEnumerableはコレクションの機能を持っていますので、この後のプログラムはListの代わりにIEnumeralbe型を使って記述することができるでしょう。どうしてもListが必要であれば ToList メソッドを使ってList型に戻すことができます。
いかがでしょうか。ジェネリック型の代入はわけがわからないと感じたなら、あなたの感性は正常です。なぜこのようなことになるのかという理屈はこの記事では省略していますけどまた難しいです。どうか気にしないでください。最初にも書いたようにこのことは気にせずプログラムしてVisual Studioがエラーメッセージを表示したときに、どうするか考えましょう。それに型パラメーターが違う代入というのはあまり使わないものです。 この手のことにはたいてい深い事情があり、どういう事情か調べていくといろいろ知識も増えるものですが、ここで時間を使っているより新しいことの習得に時間を使った方が有意義だと私は思います。理屈が気になる方はこのあたりで解説されています。 |
発展学習では意欲的な方のために現段階では特に理解する必要はない項目を解説します。 IEnumerableのリファレンスには型パラメーターが「共変」であると明記されており、Listのリファレンスにはそうは書かれていません。なお、仕様上Listのようなクラスで型パラメーターに共変性を持たせることはできません。 IEnumerable<T> インターフェイス (System.Collections.Generic) | Microsoft Docs |
ジェネリックはクラスだけではなく、メソッドで使用される場合もあります。このようなメソッドをジェネリックメソッドと呼びます。
たとえば、指定した型の要素だけを抽出する OfType (読み方:OfType=オブタイプ)はジェネリックメソッドです。
次のプログラムでは Object型の要素を持つListを定義して、整数(Integer), 文字列(String)、小数(Double)の項目をそれぞれ2個ずつ持つようにしています。
Dim source As New List(Of Object)({123, 456, "ABC", "XYZ", 1.1, 2.2})
'String型の要素だけ抽出します。
Dim stringItems = source.OfType(Of String)
For Each item In stringItems
Debug.WriteLine(item)
Next
この中から、指定した型の値だけ抽出するのに OfType メソッドが便利に使用できます。
OfTypeメソッドの戻り値はさきほど少し登場した IEnumerable型であり、コレクションの機能を持っているので、これに対してFor Eachするなどして個々の値を取り出すことができます。
このプログラムを実行すると、"ABC" と "XYZ" が出力されます。
OfTypeでは、抽出対象の型を指定する必要があるのですが、通常のメソッドの引数だと型ではなく値を指定するので、型を指定するというのはなかなか面倒なものです。一方、ジェネリックの型パラメーターを使うと直接型を指定できるので、このような機能はとても使いやすくなります。
本文ではListの例を紹介していますが、OfTypeメソッドは、List以外でも配列やStackなどの各種コレクションで使用できます。たとえば、次の例では、配列に対して
OfType メソッドを使用します。
|
ArrayクラスのIndexOfメソッドは配列から要素を検索する機能で、これもジェネリックメソッドです。配列を対象にしたメソッドなので使い方がちょっと前時代的ですが、題材にちょうどよいので、少し一緒に見てみましょう。
次のプログラムは文字列型の配列から値を検索します。
Dim source As String() = {"い", "ろ", "は", "に", "ほ", "へ", "と"}
Dim index As Integer = Array.IndexOf(Of String)(source, "ほ")
Debug.WriteLine(index) '4 と出力されます。
このIndexOfメソッドには、いくつかオーバーロードがあります。この例で使用しているオーバーロードの定義は次の通りです。
慣れないうちは、この定義が意味するところがどういうことかわかりにくいと思うので、少し分解して考えてみましょう。
①のPublicはこのメソッドがどこからでも呼び出せることを示しています。②のSharedはこのメソッドが共有メソッドであることを示しています。つまり、クラスのインスタンスから呼び出すのではなく、クラスから直接呼び出す使い方をするということです。③のFunctionはこのメソッドに戻りがあることを示しています。④のIndexOfはこのメソッドの名前です。飛ばして最後の⑦はこのメソッドの戻り値です。
ポイントは⑤と⑥です。⑤はこのIndexOfメソッドに与える型パラメーターを示しています。Tという名前の型パラメーターを1つ指定できることが示されています。⑥はこのIndexOfメソッドの通常の引数です。引数は2つあって、1つ目の引数の名前はarrayで型は T の配列です。T というのは⑤で指定する型パラメーターのことです。つまり、⑤でIntegerを指定した場合、第1引数arrayはIntegerの配列ということになります。このように、ジェネリックメソッドは型パラメーターを使って自分自身の定義やプログラムに含まれる型を変えることができるのです。これはすごい!2つ目の引数は名前は value で 型は T です。こちらは () がついていないので配列ではありません。
機能の観点で眺めると、IndexOfメソッドは第1引数 array の中から、第2引数 value を探し出して、その位置を返します。配列にはいろいろの型があるので、このように型パラメーターを持つジェネリックメソッドにすることでいろいろな型に1つの定義で対応できるようにしているわけです。
なお、このArray.IndexOfというメソッドはあまり重要ではなく、私がここで文字数を割いてみなさんに理解してもらいたいのは、この手のヒントの見方です。Visual Studioを扱っているとこのようなヒントが頻繁に表示されますが、ジェネリックを使う場合などはヒントの表記が複雑になり、せっかくのヒントも何をどう見ればよいのかわからないというもったいない状況になってしまいます。そうならないようにArray.IndexOfを題材にしてここで説明しています。
以上を踏まえてVisual Studio のヒントにも注目してみましょう。
型パラメーターを入力する直前では、Visual Studioは次のようなヒントを表示します、
ここでは型パラメーターが T と表現されていて、第1引数、第2引数でも型が T で表現されています。
続けて、型パラメーターに String を入力して、引数の入力をする時点では、Visual Studio のヒントは 次のように T だった部分が String に変化しています。
型パラメーターにStringを入力したことで T が String であることが確定したので、それに応じてヒントも自動的に切り替わったのです。
Stringではなく、IntegerやPointなど別の型を指定すると、ヒントもそれに応じたものに変わります。
このように型パラメーターは、型の定義を柔軟に変化させる目的で使用できるということを理解していただけと思います。
型パラメーターが推論可能な場合、型パラメーターの指定は省略することができます。
Array.IndexOf メソッドの例をもう1度眺めてみます。
Dim index As Integer = Array.IndexOf(Of String)(source, "ほ")
型パラメーターに String を指定しているので、第2引数に "ほ" のような String型の値が指定できているわけですが、逆に考えると第2引数が "ほ" であるならば、型パラメーターは String 以外ありえないということになります。
VBもこのような推論が可能なので、実際のところこの例では(Of String) を省略して次のように書くことができます。
Dim index As Integer = Array.IndexOf(source, "ほ")
VBは引数の型を手がかりに型パラメーターを推論します。型パラメーターの推論に使用できる引数がない場合、型パラメーターを省略できません。
たとえば、前述したOfTypeメソッドは、指定した型の要素だけの新しいコレクションを作成する機能で次のようにしようしました。
Dim source As New List(Of Object)({123, 456, "ABC", "XYZ", 1.1, 2.2})
'String型の要素だけ抽出します。
Dim stringItems = source.OfType(Of String)
For Each item In stringItems
Debug.WriteLine(item)
Next
この場合、(Of String)を指定することで、文字列型の要素を抽出したいということを明示しています。この(Of String)を省略してしまうと、何がしたいのか推論する手がかりが一切なくなってしまうので、この例では(Of String)を省略するとエラーになります。
自分でジェネリックメソッドを作成する場合も、Of を使って型パラメーターを定義します。
次のような構文になります。
Public Function メソッド名(Of 型パラメーターリスト)(パラメーターリスト) As 戻り値の型
実行する処理
End Function
通常の関数と同様に戻り値がない場合はFunctionの代わりにSubを使用します。Publicの部分もアクセスレベルに応じてPrivateやProtectedなど他の物を指定することもできます。
次の例は、単純なジェネリックメソッドの例です。
この例では、型パラメーターで受け取った型の完全修飾名(=名前空間を含んだ型の名前。たとえば、System.String)を取得します。
Private Function GetTypeName(Of T)() As String
Return GetType(T).FullName
End Function
VBでは、GetType(読み方:GetType=ゲットタイプ)を使って、型から、型の情報を持つType型のオブジェクトを生成することができます。TypeクラスのFullNameプロパティを使うとその型の完全修飾名を取得できます。
呼び出し側は次のように記述できます。
Dim typeName As String = GetTypeName(Of Debug)()
Debug.WriteLine(typeName) 'System.Diagnostics.Debug と出力されます。
このような単純な型パラメーターのみのメソッドは、情報量が少ないため、あまり大した処理は記述できません。
もう一例紹介しておきます。次の例は指定した型で定義されているプロパティの一覧を取得します。
''' <summary>
''' Publicで定義されているプロパティの一覧を取得します。
''' 共有プロパティは取得できません。
''' </summary>
''' <typeparam name="T">対象の型を指定します。</typeparam>
''' <returns>プロパティの名前の一覧</returns>
Private Function GetProperties(Of T)() As IEnumerable(Of String)
'抽出対象を指定するフラグを作成します。
'ここでは Public かつ 非共有メンバー かつ 基底クラスも含むものを指定しています。
Dim flag As Reflection.BindingFlags = Reflection.BindingFlags .Public Or
Reflection.BindingFlags .Instance Or
Reflection.BindingFlags .FlattenHierarchy
'フラグが示すプロパティを抽出し、名前だけの一覧を作成し、重複している名前を除去します。
Dim propNames = From prop In GetType(T).GetProperties(flag) Select prop.Name Distinct
Return propNames
End Function
このプログラムアGetTypeで取得したType型のオブジェクトで、GetPropertiesメソッドを使うとプロパティの一覧を取得できることを利用しています。抽出を実行しているFrom prop In ... の部分は LINQ という技術で、初級講座第37回 で説明します。ここではあまり内容に踏み込まないことにしましょう。
この例は次のように呼び出せます。
Dim propertyNames = GetProperties(Of String)()
For Each propertyName As String In propertyNames
Debug.WriteLine(propertyName)
Next
これを実行すると Chars と Length が出力されます。StringのPublicな非共有プロパティはこの2つだけということですね。
引数のあるジェネリックメソッドはこれに通常のように引数を追加するだけです。
次の例では引数に IEnumerable をとり、型パラメーターで指定した型の最初の要素を取得します。IEnumerableはコレクションの一般的な機能を表すインターフェースで(まだ説明していないインターフェースがたびたび登場して申し訳ありませんが)、List や配列などをまとめて表現したいときに便利な型です。
Private Function GetFirstItem(Of T)(items As IEnumerable) As T
For Each item In items
If TypeOf item Is T Then
'この型変換は必要。
'コンパイラーにReturnする型と関数の戻り値で定義されている方が同じだと伝えるため。
Return DirectCast(item, T)
End If
Next
End Function
次のように呼び出せます。
Dim source As New List(Of Object)({123, 456, "ABC", "XYZ", 1.1, 2.2})
Dim firstDouble As Double = GetFirstItem(Of Double)(source)
Debug.WriteLine(firstDouble) ' 1.1 と出力します。
単純な型パラメーターだけのジェネリックメソッドが大したことができない最大の理由はインスタンスを作成できないからです。
たとえば、次のように型パラメーターで指定された型に対してNewを使用するプログラムはエラーになります。
Private Sub MyGenericsMethod(Of T)()
'この例はエラーです。
Dim instance As New T
End Sub
なぜこれができないかというと、New T という記述は T型のコンストラクターを呼び出してインスタンスを生成するということを意味しています。ところが、コンストラクターはクラスによって異なっており、引数の数や意味が異なります。
New T という記述だと引数なしのコンストラクターを呼び出すという記述になっていますが、T型にそのように呼び出せるコンストラクターが存在しているかどうか判断できません。VBやC#のような強い型付けの言語はこのように型に対する操作は事前に実行できることがわからない場合エラーにしてそもそも実行できないようにします。このようにして実行する前に早めに問題がある可能性をプログラマーに気が付いてもらいたいという趣旨なのです。
そこで、T には引数なしのコンストラクターを持つ型しか指定できないという特別な指示を追加することができます。ジェネリックの型にこのような条件を付けることを「制約」(せいやく)と呼びます。制約は As とともに使用します。これまでキーワード As は型を指定するときにだけ使っていましたが、ここでは制約を指定するという新しい使い方をすることになります。混同しないように気を付けてください。
引数なしのコンストラクターを持つ型しか指定できないという制約はキーワード New で表現します。
この制約を追加すると、上述のプログラムは次のようになりエラーにはならくなります。
Private Sub MyGenericsMethod(Of T As New)()
'この例は実行できます。
Dim instance As New T
End Sub
この制約を追加したので、呼び出し側は自由な呼び出しはできなくなります。
たとえば、 MyGenericsMethod(Of String)() という呼び出しはできません。Stringには引数なしで呼び出せるコンストラクターがないからです。
MyGenericsMethod(Of Random)() という呼び出しは可能です。Random は引数なしで呼び出せるコンストラクターがあるからです。このように「制約」を付けることで、呼び出し側で指定できる型が制約されるというわけです。
発展学習では意欲的な方のために現段階では特に理解する必要はない項目を解説します。 ジェネリックの型パラメーターから引数ありのコンストラクターを呼び出すことも可能です。リフレクションというカテゴリーの機能を利用することになります。しかし、引数ありの場合、引数の型や数や順番がクラスによって異なるので、型パラメーターでいろいろな型を指定できるようにするという方向性と逆行しがちです。後述するクラスの制約と組み合わせれば役に立つ場面もあるかもしれませんが、あまり使いたい技ではないです。 次の例は、文字列型の1つの引数を持つコンストラクターを呼び出して型パラメーターのインスタンスを生成します。
この例は DriveInfoクラスを念頭に作成しており、を試すには、MyGenericsMethod(Of IO.DriveInfo)() のように呼び出します。 |
ジェネリックメソッドの型で困ったことがあったら制約を思い出してください。
制約は、指定できる型を限定する代わりにメソッド内でできることを増やしてくれます。
ここでは、New制約以外の制約を簡単に紹介します。
参考:
ジェネリック型 - Visual Basic | Microsoft Docs
As 型名 で制約すると、その型で制約することができます。その場合、型パラメーターにはその型か、その型の派生クラスしか指定できなくなります。構造体はこの制約では対象にできません。
次の例では、型パラメーター T には Control型か、その派生クラスしか指定できなくなります。
Private Sub MyGenericsMethod(Of T As Control)()
End Sub
この制約は強力で、指定できる型がかなり限定されますが、限定が大きい分、使える機能が増えます。その型のもつメソッドやプロパティが使用できることが保証されるからです。
As インターフェースで制約すると、指定したインターフェースを持つ型しか指定できないように制約できます。
次の例では、型パラメーター T には IEnumerable型か、それを実装する型しか指定できなくなります。
Private Sub MyGenericsMethod(Of T As IEnumerable)()
End Sub
クラスである型しか指定できないように制約できます。キーワード Class を使用します。
次の例では、型パラメーター T には クラスである型しか指定できなくなります。逆に言うと構造体を指定できなくなります。たとえば、IntegerやDateは構造体なのでこの型パラメーターTには指定できなくなります。
Private Sub MyGenericsMethod(Of T As Class)()
End Sub
構造体である型しか指定できないように制約します。キーワード Structure を使用します。
次の例では、型パラメーター T には構造体であるInteger や Date、 Point などは指定できますが、String や StremWriter などのクラスは指定できなくなります。
Private Sub MyGenericsMethod(Of T As Structure)()
End Sub
複数の制約を指定する場合は { } 内にカンマで区切って制約を並べます。
次の例では、型パラメーター T には Control型か、その派生クラスで、かつ、引数なしのコンストラクターを呼び出せるものしか指定できなくなります。
Private Sub MyGenericsMethod(Of T As {Control, New})()
End Sub
なお、これを使ってもクラスの制約は1つしか指定できません。
メソッド単位ではなく、クラスレベルで型パラメーターを使用することもできます。ListやDictionaryなどで既に使っているものです。
使用する側ではコンストラクターで型パラメーターを指定することになります。
次の例はこれまでもよく出てきている例ですが、List(Of T)クラスを型パラメーターに String を指定してインスタンス化している例です。
Dim items As New List(Of String)
ジェネリッククラスを自作する場合は、次のように型パラメーターを指定します。
Public Class MyPoint(Of T)
Public Property X As T
Public Property Y As T
End Class
コレクションのように値を単なるデータとして扱う場合は、これでも良いのですが、値を使って何か処理することを考えるとジェネリッククラスはなかなかたいへんです。型の実体が何かわからないからです。
たとえば、上述のMyPointクラスはXプロパティとYプロパティが T型になっています。私はなんとなく、T は Integer とか Double とか、Decimal のような数値が指定されることをイメージしてこの例を書きましたが、機能上はそのような制約は一切ないので T には String や Date や StreamWriter などが指定されるかもしれません。
その可能性を考慮すると X や Y を使った計算処理は記述できません。プログラムしている本人は数値しか指定しないつもりでたし算やかけ算を実行するプログラムを書きたいかもしれませんが、制約のところで説明したようにVBはそのようなプログラムを許可しないので、このクラスに次のようなメソッドを記述することはできません。
Public Function IsPositive() As Boolean
If X > 0 AndAlso Y > 0 Then
Return True
End If
Return False
End Function
値を比較する X > 0 と Y > 0 の部分が、 X と Y が数値なら問題ないのですが大小比較できないような型が T に指定される可能性があるからです。CInt や CLng でこの部分を強制的に数値に変換することもできません。CInt や CLng で変換できる型かどうかわからないのでコンパイラーはエラーにします。
ジェネリッククラスを採用する際はこのようなことも考慮してください。
なお、この例でどうしてもXやYを数値型に変換したい場合、Convertクラスのメソッドを使用することは可能です。たとえば、次の例は動作します。
Public Function IsPositive() As Boolean
Dim intX As Integer = Convert.ToInt32(X)
Dim intY As Integer = Convert.ToInt32(Y)
If intX > 0 AndAlso intY > 0 Then
Return True
End If
Return False
End Function
ただ、この例だと、X や Y が Integer に変換できることが前提になってしまい、ジェネリックを使用する意味がなくなってしまいます。
ジェネリッククラスを継承することもできます。たとえば、List(Of T)を継承した独自の文字列のコレクションを作成することができます。
次の例では MyList クラスは List(Of String)を継承しているので、List(Of String)のすべての機能を持ちます。
Public Class MyList
Inherits List(Of String)
Public Function GetLongest() As String
Dim longest As String = ""
For Each item As String In Me
If Len(item) > Len(longest) Then
longest = item
End If
Next
Return longest
End Function
End Class
この例では、For Each のループで In Me というように Me が使われていることにも気を付けてください。自分自身がコレクションなので、Me を指定してループできます。
このクラスの呼び出し例は次の通りです。
'List(Of String)を継承しているので From での初期化も可能
Dim items As New MyList From {"Apple", "Banana"}
'List(Of String)を継承しているので Add も使用できる。
items.Add("Cat")
items.Add("Dog")
'追加したメソッドを呼び出して、最長の項目を取得します。
Dim longestItem As String = items.GetLongest
型パラメーターを保ったまま継承するには次のようにします。
Public Class Class MyList2(Of T)
Inherits List(Of T)
'内容は省略
End Class