Visual Basic 初級講座 [改訂版] |
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
Visual Basic 中学校 > 初級講座[改訂版] >
2021/4/11
この記事が対象とする製品・バージョン
![]() |
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 | × | 対象外です。 |
目次
モジュールは、クラスの特別な形態です。
ソリューションエクスプローラーでプロジェクトを右クリックして [追加] - [新しい項目] から「モジュール」を選択すると追加できます。
Visual Studio でモジュールを追加すると次のようなプログラムのあるというファイルが追加されます。既定のファイル名は Module1.vb です。
Module Module1
End Module
このように、モジュールは構文上は Module ~ End Module で囲んで定義します。
モジュールとはクラスの違いは次の通りです。
モジュールについては、初級講座[改訂版] 第7回でも説明しています。第7回では使用する観点から説明しており、VBにデフォルトで用意されているモジュールも紹介してます。 今回は作成する立場からモジュールを説明します。 |
それでは、モジュールでの基本的なプログラムを見てみましょう。
Module Module1
Public Function Add(x As Integer, y As Integer) As Integer
Return x + y
End Function
End Module
このAddメソッドは共有メソッドになります。クラスであれば共有メソッドの宣言には Shared キーワードが必要ですが、モジュールのメンバーはすべて共有メンバーになるため Shared キーワードを付ける必要はありません。むしろSharedを付けるとエラーになります。
これを呼び出すプログラムではモジュール名を書く必要がないので同じプロジェクト内であれば単純に Add というメソッド名だけで呼び出せます。
Dim answer As Integer = Add(2, 3)
モジュール名を付けても呼び出すことができるので、あえて明記したい場合は、モジュール名を記述してもよいです。
Dim answer As Integer = Module1.Add(2, 3)
別プロジェクトで定義されているモジュールの場合は、そのモジュールがスコープ内にある場合は、やはりメンバー名だけで呼び出すことができます。
「スコープ内にある」とは、記述すれば呼び出せる状態になっていることを指しています。
たとえば、ProjectAプロジェクトの Module1 で定義されている Addメソッドを ProjectB から呼び出したい場合、ProjectB から ProjectA に参照設定をし、Addメソッドを呼び出したいプログラムのファイルの先頭に Imports ProjectA と記述することで Module1 がスコープ内になります。
拡張メソッドは、本来メソッドが定義されているクラスなどの外側でメソッドを定義する方法です。
本来のメソッドはそれを定義するクラスのインスタンスとともに次のように使用します。
Dim instance As New ClassName
instance.MethodName(arg1, arg2)
■呼び出し例1:本来のメソッド
この例では、ClassName というクラスの MethodName メソッドを呼び出しています。
これは架空の例で、 MethodName メソッドを呼び出すと何が起きるのかは特に重要ではありませんが、同じ処理を実行する別のメソッド ExtensionMethodName メソッドを実装したモジュールがあると想定してみましょう。
モジュールだと呼び出し時にモジュール名を省略できますから、次のように呼び出して同じ処理を実行することができます。
ExtensionMethodName(instance, arg1, arg2)
■呼び出し例2:モジュールのメソッド
引数は1つ増えて instance も引数として必要です。処理の内容が不明なので instance は不要かもしれませんが、処理の内容次第なのでinstance自身も引数にしておくのが確実です。
さて、VB2008のときに構文をちょっと改造して、次の構文で呼び出し例2と同じ呼び出しが実行できる機能が導入されました。これが拡張メソッドです。
instance.ExtensionMethodName(arg1, arg2)
■呼び出し例3:拡張メソッド
呼び出し例1にある本来のメソッドの 構文と照らし合わせると、ExtensionMethodNameが定義されているのはinstanceのように見えますが、そうではなく、これは拡張メソッドの新しい構文で、 instance は第1引数です。かっこの中に入っている arg1、arg2 は第2引数と第3引数です。ExtensionMethodNameが定義されているのはスコープ内に入っているどこかのモジュールです。
本来のメソッドの構文と拡張メソッドの構文は見かけ上まったく同じというのがポイントです。
これは、実体はそうではないのに、まるで通常のメソッド呼び出しのように見せかけるシュガーシンタックス(糖衣構文)と呼ばれる考え方です。通常のメソッド呼び出しのように使えたら便利でしょう。という発想です。
Stringクラスを例に実際の拡張メソッドを見てみましょう。
Stringクラス(文字列型)の変数に . をつけてメンバーの一覧を見てみると、 次のようにいろいろなメソッドやプロパティが表示されます。
この中で ↓ がついたアイコンで表示されているメソッドは拡張メソッドです。
インテリセンスのヒントの中にも <拡張> と書いてあり拡張メソッドであることを示しています。
つまり、これらのメソッドはStringクラスのメンバーではないのに、まるで StringクラスのメンバーであるかのようにVisual Studioに表示され、実際にStringクラスのメンバーであるかのように使用できるということです。
Stringクラスにメソッドを追加したいなと思ってもStringクラスのプログラムを修正することはできません。でも、拡張メソッドであればStringクラスのプログラムを修正することなく、あたかもStringクラスのメソッドであるかのように使うことができるので便利です。モジュールを使えばこれが可能です。
たとえば、次のプログラムは文字列に含まれる文字数を数えるために Count メソッドを使用しています。
Dim value As String = "Hello!"
Dim count As Integer = value.Count()
Debug.WriteLine(count) '6 と出力されます。
この Count メソッドも拡張メソッドです。
VB2019の場合、Countを右クリックして「定義へ移動」を選択すると、いろいろなメソッドの一覧が表示されます。この一覧は、プログラムの定義情報を解析してVisual Studioがその場で自動生成したものです。上までスクロールすると、Public NotInheritable Class Enumerable と表示されています。
つまり、Count メソッドを本当に持っているのは Enumerableクラスなのです。(EnumerableクラスはC#でプログラムされており、Visual Studioが定義情報を解析すると NotInheritable Class という判定になります。VBで通常これに相当するのは今回説明しているモジュールです。クラスとモジュールは .NET のレベルから見れば同じものです。)
本来定義されているクラス(モジュール)が Enumerable であるとわかったので、先ほどのプログラムは次のように書き換えることができます。
Dim value As String = "Hello!"
Dim count As Integer = Enumerable.Count(value)
Debug.WriteLine(count) '6 と出力されます。
メソッド呼び出し部分だけ、取り出して比較してみましょう。
拡張メソッドとして呼び出しているとき value は実は第1引数の指定なのです。そして、Enumerable というクラス(モジュール)名は完全に隠されています。
Stringクラスにもし本当に Count という名前のメソッドが同じシグネチャ(引数の数・型・順番)で存在する場合、本当の Count メソッドの方が拡張メソッドより優先して呼び出されます。
フレームワークの設計はよくできているので、そのような自体で困ることはありませんが、もし名前が衝突する場合拡張メソッドの方を呼び出すには Enumerable.Count のように本来の呼び出しを使用することになります。
拡張メソッドは第1引数で指定されたクラスを拡張します。
Countメソッドの場合、右クリックして「定義へ移動」で表示された定義は次の通りです。
Public Shared Function Count(Of TSource)(source As IEnumerable(Of TSource)) As Integer
少し小難しい定義になっていますが、第1引数は source という名前であり、 IEnumerable(Of TSource) 型 ということになっています。
Countメソッドが拡張メソッドとして機能するのは Stringクラスではなく、IEnumerable(Of TSource) 型ということです。
Of はジェネリックによる型パラメーターなので、実際に使用するときは IEnumerable(Of String) や IEnumerable(Of Integer) となります。
ジェネリックについては、初級講座[改訂版] 第16回で、Listクラスを説明するときに少し説明しています。型を引数のように指定する機能を「ジェネリック」と呼び、Ofに続いて指定される型を「型パラメーター」と呼ぶのでした。 |
試しに次のようなプログラムを書いてみると Count メソッドが拡張メソッドとして使用できることが確認できます。
このプログラムは動作しませんが、拡張メソッドが使えるかどうか確認するのには十分です。
Dim test As IEnumerable(Of Integer)
Dim result = test.Count
しかし、さきほどの例では IEnumerable(Of xxx)を拡張するのではなく、Stringクラスが拡張されていました。
これはどういうことでしょうか?
ここで、Microsoft Docs を使って Stringクラスの定義を確認してみましょう。
String クラス (System) | Microsoft Docs
次のように書いてあります。
Public NotInheritable Class String
Implements ICloneable, IComparable, IComparable(Of String), IConvertible, IEnumerable(Of Char), IEquatable(Of String)
キーワード Implements (読み方:インプリメンツ)は初級講座では深入りしない予定ですが、インターフェースの実装を表しています。
つまり、この後で列挙されている IClonealbe や IComparable といった「インターフェース」が String クラスでは使用できるという意味です。
ひとまずメソッドの組み合わせを定義したものと理解しておいてください。(プロパティが定義されている場合もゼロではありませんがレアケースです。) IEnumerableインターフェースではたった1つ GetEnumerator というメソッドが定義されています。他のインターフェースでは複数のメソッドが定義されている場合もあります。 インターフェースについては、初級講座[改訂版] 第28回で、少し説明しています。 |
Stringクラスが実装しているインターフェースの中には IEnumerable(Of Char)というものがあります。
これが Stringクラスで拡張メソッド Count が使用できる理由です。
ぴんと来ない方もいると思うので、少し実験してみましょう。
次のプログラムは実際に動作します。
Dim value As String = "Hello!"
Dim test As IEnumerable(Of Char) = value
Dim count As Integer = test.Count()
Debug.WriteLine(count) '6 と出力されます。
ポイントは String である value を IEnumerable型の変数 test に代入している点です。
String は IEnumerable でもあるのでこのような代入が許可されます。
※第28回で、変数に別の変数を代入できる3つ目の条件として、「a の型が b が実装しているインターフェースである場合」と挙げておりこれに該当します。つまり「test の型が String が実装しているインターフェース IEnumeralbeである」ので代入できるということです。
というわけで、わざわざ別の変数に代入せずとも、次のようにString型の変数から直接拡張メソッド Count を使用できます。
Dim value As String = "Hello!"
Dim count As Integer = value.Count()
Debug.WriteLine(count) '6 と出力されます。
インターフェースがかかわっていて話が難しくなってしまいました。拡張メソッドとインターフェースには直接関係なく、String や Integer などのクラスを直接拡張する拡張メソッドを定義・使用することもできます。この後でインターフェースを使わないで拡張メソッドを作成する例を紹介しますので、そちらで実際に試していただければと思います。
このような事情なので IEnumerable インターフェースを実装しない、Integer や Date では、Count拡張メソッドは使用できないので . (ドット) を入力して、メンバー一覧を見ても Count は表示されません。
次の条件を満たすメソッドが拡張メソッドになります。
モジュール自体を Public にしないと
第1引数で指定された型を拡張します。そのため引数は少なくとも1つ必要です。
Public Module モジュール名
<System.Runtime.CompilerServices.Extension>
Public Sub 拡張メソッド名(source As 拡張する型)
拡張メソッドの処理
End Sub
End Module
このように定義すると、モジュールがスコープ内にある時次のように呼び出すことができます。
Dim value As 拡張する型
value.拡張メソッド名
第2引数以降を定義することもできます。
Public Module モジュール名
<System.Runtime.CompilerServices.Extension>
Public Sub 拡張メソッド名(source As 拡張する型, arg1 As 型1, arg2 As 型2)
拡張メソッドの処理
End Sub
End Module
このように定義すると、モジュールがスコープ内にある時次のように呼び出すことができます。
Dim value As 拡張する型
value.拡張メソッド名(arg1, arg2)
1つのモジュールに複数の拡張メソッドを定義することもできます。モジュール内に拡張メソッドでない通常のメソッドを混在させることもできます。
なお、「拡張プロパティ」という機能はありませんので、Property に Extension属性をつけても無駄です。それから、共有メソッドのように使用できる拡張メソッドも作成できません。
それでは、自分で拡張メソッドを作ってみて理解を深めましょう。
ひらがなをカタカナにする ToKatakana メソッドをStringクラスの拡張メソッドとしてプログラムすると次のようになります。
Public Module StringExtention
''' <summary>文字列に含まれるひらがなをカタカナに変換します。</summary>
<System.Runtime.CompilerServices.Extension>
Public Function ToKatakana(source As String) As String
Dim result As New System.Text.StringBuilder(Len(source))
'Unicodeに対応して1文字ずつ抽出する列挙子を取得します。
Dim itor = Globalization.StringInfo.GetTextElementEnumerator(source)
'列挙子から1文字ずつ抽出します。
Do While itor.MoveNext()
'現在の文字の文字コードを取得します。
Dim current As Integer = AscW(itor.Current.ToString)
If current >= &H3041 AndAlso current <= &H3096 Then
'ひらがなの場合、&H60 プラスしてカタカナにしたものを結果に追加します。
result.Append(ChrW(current + &H60))
Else
'ひらがなでない場合、そのままその文字を結果に追加します。
result.Append(itor.Current.ToString)
End If
Loop
Return result.ToString
End Function
End Module
この例では Public Module 内に System.Runtime.CompilerServices.Extension属性を持ったメソッド ToKatakana があり、第1引数が String 型なので、ToKatakanaはString を拡張する拡張メソッドになります。
処理内容の説明は今回のテーマからは外れますが、簡単に中身の処理の流れを説明しておきます。
このメソッドでは引数で渡された source から1文字ずつ文字を取り出します。標準的な文字コードの体系であるUnicode(ユニコード)に完全に対応するために Globalization.StringInfo.GetTextElementEnumerator メソッドの機能で1文字ずつ取り出すことにします。このメソッドは少し面倒で、直接文字を取り出せるわけではなく、文字を取り出すためのオブジェクトを生成します。この例では itor がそれです。
itorのMoveNextメソッドを使うと1文字取り出せます。取り出した文字は Currentプロパティで表されます。もう1回MoveNextメソッドを使うと次の1文字を取り出せます。このように次々とMoveNextメソッドを呼び出していくことで順番に1文字ずつ取り出せます。もう取り出す文字がないときにはMoveNextメソッドは False を返すので、 Do ~ Loop を使って MoveNext が True である限り同じ1文字ずつ処理をするようにしました。
AscWメソッドを使うとUnicodeのコードポイントを取得できます。コードポイントとはUnicodeにおける文字コードのようなものです。この例では 変数 current にコードポイントを代入します。
Unicodeで ひらがなは コードポイント 3041 から 3096 まで割り当てられています。この 3041 や 3096 は16進数です。(3097以降のコードポイントにも濁点や踊り字などが若干定義されていますが今回のプログラムでは無視します。)
文字コードの世界は通常16進数を使用します。VBでは16進数の数値を扱うには数字の前に &H を記述します。これを使って current がひらがなであるかチェックしています。
そして、この範囲のひらがなに対応するカタカナはコードポイント 30A1 から 30F6 です。
つまり、この範囲ならばひらがなのコードポイントに &H60 をプラスすることでカタカナのコードポイントになります。
これを利用してひらがなならば &H60 を加算した文字を追加し、そうでなければその文字をそのまま追加することで、渡された引数のうちひらがなだけをカタカナに変換する機能になります。ChrWメソッドはAscWメソッドの逆でコードポイントが表す文字を取得します。
Unicodeには濁点・半濁点や、音を伸ばす ー 、今ではほとんど使われない ゝ 、 ゞ 、ゟ (より)、ヿ (コト)なども定義されています。カタカナにだけ存在する、ワ・ヰ・ヱ・ヲの濁音バージョンもあります。(なお、変体仮名はUnicodeのまったく別の領域に収録されています。) こういうわけでひらがな・カタカナ変換を厳密にやるのは少し骨が折れます。 VBのStrConvメソッドにはひらがな・カタカナ等の変換機能がありお手軽に使うには便利ですが、日本語環境のWindowsでしか動作しないなど環境上の制約があり、うまく動作するか心配なので私は業務プログラムで使うのには躊躇します。最近(2021年時点)では、作ったプログラムをクラウドで動かすことも珍しくないですし、ひらがな・カタカナ変換くらいなら1文字ずつマッピングしたほうが私は安心です。
|
同じプロジェクト内であればモジュールは常にスコープに入っているため、特に意識しなくても次のようにしてこの ToKatakanaメソッドを呼び出すことができます。
Dim value As String = "あのころはフリードリヒがいた"
Dim result As String = value.ToKatakana
Debug.WriteLine(result) 'アノコロハフリードリヒガイタ
本当に String のメソッドのように見えるので何か気分が良いですね。
別プロジェクトの場合、前回説明したようにプロジェクト参照の参照設定したうえで、モジュールを呼び出せるように名前空間を Imports すればそのモジュールがスコープに入るので拡張メソッドを呼び出すことができます。
たとえば、ClassLibrary1 の StringExtensionモジュール内に拡張メソッド ToKatakana があって、これを ConsoleApp1 の Program.vb 内のプログラムで使用したい場合は次のようにします。
ClassLibrary1側で名前空間の設定を変更している場合は、Imports 文には StringExtensionモジュールが属する名前空間を指定します。
クラスライブラリと拡張メソッドは相性がよく、便利な拡張メソッドを作成したらクラスライブラリとして保存しておけば、いろいろなプロジェクトから活用できます。プログラムすればするほどあなた専用のクラスライブラリの中に拡張メソッドが増えて効率的にアプリケーションが作れるようになっていくことでしょう。
String や Integer などの特定の型を拡張することもできますが、基本型を拡張するとその基本型から派生しているすべての型を拡張できます。また、インターフェースを拡張するとそのインターフェースを実装しているすべての型を拡張できます。
たとえば、すべての型の基本型である Object に対する拡張メソッドを定義すればすべてのクラスを拡張できます。
Visual Studio で入力候補が表示されているときに拡張メソッドのアイコンをクリックすると、拡張メソッドのみの一覧を表示することができます。逆に拡張メソッド以外のアイコンをクリックすると、拡張メソッドが表示されなくなります。
クリックする代わりに Alt + X も使用できます。
このアイコンが表示されていないときは、その状況で使用できる拡張メソッドが存在しないことを示しています。