モジュール(module)とは、VB6以前のbasファイルのように、関数(もしくはプロシージャ・メソッド)やそれに関連する変数など、一連のソースコードをひとかたまりにしておくためのものです。

内部に関数と変数を持てる点ではクラスに類似した言語要素ではありますが、クラスとは異なりモジュールはインスタンス化せずに用いることができる(§.モジュールとクラスの違い)、モジュール名を省略した呼び出しができる(§.モジュール名を省略したメンバの参照)などの違いがあります。 一般に、クラスはデータとそれを操作する手続きを構造化する目的で使われるのに対し、モジュールは手続きのみを構造化するために使われます。

VB6以前ではbasファイル自体が一つのモジュールとなっていましたが、 VB.NETでは一つのファイルに一つ以上のモジュールを記述することができます。 また、VB6以前にあった「basファイルは標準モジュール、clsファイルはクラス」といった拡張子による区別もなくなり、モジュールとクラスはどちらもvbファイルに記述でき、一つのファイルにモジュールやクラスを混在させたり複数記述させたりすることが出来るようになっています。 名称も、標準モジュールから単にモジュールと呼ばれるようになっています。

モジュールの宣言

VB.NETでのモジュールは、VB6以前のようにファイル自体がモジュールとなるのではなく、Moduleキーワードを使って明示的に宣言する必要があります。

モジュールの宣言
Module MainModule

    Sub Main()

        Console.WriteLine("Hello, world!")

    End Sub

End Module

これはもっとも基本的な「Hello, world!」プログラムですが、これがVB.NETプログラムの基本的な形でもあります。 ModuleキーワードからEnd Moduleキーワードまでがモジュールとなり、モジュール名MainModuleとして宣言しています。

モジュールという概念がわかりにくいのであれば、ModuleEnd Moduleを一つのbasファイルと見なして考えるのもいいかもしれません。

モジュールは一つのvbファイルに複数記述できるので、次のようなコードも記述できます。

一つのファイルで複数のモジュールを宣言する
' 一つ目のモジュール
Module MainModule

    :
    :

End Module

' 二つ目のモジュール
Module SubModule

    :
    :

End Module

すでに述べたように、一つのファイルでモジュール以外にもクラス構造体を任意個宣言することができます。

一つのファイルでモジュール・クラス・構造体を宣言する
' モジュールの宣言
Module MainModule

    :
    :

End Module

' クラスの宣言
Class SampleClass

    :
    :

End Class

' 構造体の宣言
Structure SampleStruct

    :
    :

End Structure

モジュールとクラスの違い

VB.NETにはモジュールだけでなくクラス(Class)という類似した要素が存在します。 クラスについてはクラスで詳しく説明しますが、モジュールとクラスの相違点を簡潔にまとめると次のようになります。

モジュールとクラスの相違点
機能 モジュール クラス
インスタンス化 できない できる
継承 できない できる
インターフェイスの実装 できない できる
メソッドとメンバ変数 すべて暗黙的に共有メソッド・共有メンバとなる Sharedを付ければ共有メソッド・共有メンバとなる
型名を省略したメンバの参照 できる できない
拡張メソッドの宣言 できる できない

モジュールは特殊な形態のクラスととらえることができます。 特に、すべてが共有メンバとなり、インスタンスを生成できないクラスという点で、モジュールはC#における静的クラス(static class)とほぼ同等のものとなります。

モジュールとクラスのどちらを使うべきかは状況によりますが、一般に、ユーティリティメソッドなど様々な場面で呼び出されるような共通処理などを記述する場合はモジュールが向いています。 一方、インスタンス化できるのはクラスとなるので、内部に状態を持つオブジェクトを表現するような場合はクラスが向いています。

変数の宣言

モジュール内で変数を宣言するには、プロシージャ内などで変数を宣言するのと同様、Dimを使います。 変数の宣言と同時に、初期値を与えることもできます。 モジュール内で宣言した変数は、そのモジュールのどこからでも参照することが出来ます。

Module MainModule

    ' モジュール内変数の宣言と初期値の代入
    Dim message As String = "Hello, world!"

    Sub Main()

        Console.WriteLine(message)

        ' モジュール内変数の値を変更する
        SetMessage("こんにちわ世界")

        Console.WriteLine(message)

    End Sub

    ' モジュール内変数の値を設定するプロシージャ
    Sub SetMessage(ByVal newMessage As String)

      ' 引数で指定された値を設定する
      message = newMessage

    End Sub

End Module
実行結果
Hello, world!
こんにちわ世界

Dimで宣言した変数はモジュール内でのみ有効であり、他のモジュールやクラスからは参照することは出来ません。 一方、Dimの代わりにPublicで宣言すれば、他のモジュールやクラスからも参照することが出来るようになります。 他のモジュールで宣言されている変数を参照するには、モジュール名.変数名と記述します。

Module MainModule

    Sub Main()

        ' モジュールSubModuleの変数Messageの内容を表示する
        Console.WriteLine(SubModule.Message)

        ' 他のモジュールの変数の値を変更する
        SubModule.Message = "こんにちわ世界"

        Console.WriteLine(SubModule.Message)

    End Sub

End Module

Module SubModule

    ' 他のモジュールからも参照できる変数
    Public Message As String = "Hello, world!"

End Module
実行結果
Hello, world!
こんにちわ世界

なお、必要に応じてモジュール自体にもPublicなどのアクセス修飾子を付けることが出来ます。 モジュールのアクセス修飾子とメンバのアクセス修飾子の組み合わせによって、モジュール外部からのアクセスをどこまで許可するかを定めることが出来ます。

DimPublicの違いや詳細など、アクセス修飾子についてはアクセス修飾子を参照してください。

モジュール名を省略したメンバの参照

モジュール同士が同一の名前空間にあるか、もしくはImportステートメントでモジュールの名前空間がインポートされている場合は、他のモジュールのメンバを参照する際にモジュール名を省略することが出来ます。

先にあげた例でも2つのモジュールは同一名前空間にあるためモジュール名を省略することができます。 改めて例を挙げると、次のように記述することが出来ます。 この例では名前空間(Namespace)が省略されていることからどちらもルート名前空間に属することになり、つまりどちらも同一名前空間のモジュールとなるため、モジュール名を省略することができます。

モジュール名を省略したメンバの参照
' MainModuleとSubModuleはどちらも同一の名前空間(ルート名前空間)に属する
Module MainModule

    Sub Main()

        ' モジュールSubModuleの変数Messageの内容を表示する
        ' (SubModuleはMainModuleと同一の名前空間にあるため、モジュール名の記述を省略できる)
        Console.WriteLine(Message)

        ' 他のモジュールの変数の値を変更する
        Message = "こんにちわ世界"

        Console.WriteLine(Message)

    End Sub

End Module

Module SubModule

    ' 他のモジュールからも参照できる変数
    Public Message As String = "Hello, world!"

End Module
実行結果
Hello, world!
こんにちわ世界

この例ではモジュール内の変数を参照していますが、モジュール内のプロシージャを呼び出す場合も同様にモジュール名を省略することができます。


異なる名前空間で宣言されているモジュールに対してモジュール名の記述を省略したい場合は、次の例のようにImportステートメントでモジュールが宣言されている名前空間をインポートします。 なおこの例では、プロジェクト設定でルート名前空間がConsoleApp1に設定されているものとします。

異なる名前空間内にあるモジュールでのモジュール名の省略
' SubModuleが宣言されている名前空間をインポートする
Imports ConsoleApp1.SubNamespace

Module MainModule

    Sub Main()

        ' モジュールSubModuleの変数Messageの内容を表示する
        ' (SubModuleが宣言されている名前空間をImportしているため、モジュール名の記述を省略できる)
        Console.WriteLine(Message)

        ' 名前空間がImportされていない場合は、下記のようなエラーとなる
        Console.WriteLine(Message) ' error BC30451: 'Message' は宣言されていません。アクセスできない保護レベルになっています。

    End Sub

End Module

' 名前空間SubNamespace内でモジュールSubModuleを宣言する
Namespace SubNamespace

    Module SubModule

        ' 他のモジュールからも参照できる変数
        Public Message As String = "Hello, world!"

    End Module

End Namespace

実行結果
Hello, world!

vbNullvbNewLineといったVB固有の定数群はConstantsモジュールで宣言されているものですが、モジュール名を省略して使用できるのはこの機能により実現されています。

入れ子

モジュールはクラスとは異なり、入れ子にすることはできません。 例えば次のようなコードはコンパイルエラーとなります。

入れ子になったモジュールは宣言できない
Module MainModule

    Sub Main()

        ' 入れ子になったモジュール内の変数を参照したい
        Console.WriteLine(SubModule.NestedModule.Message)

    End Sub

End Module

Module SubModule

    ' 入れ子になったモジュールはコンパイルエラーとなり、宣言できない
    Module NestedModule ' error BC30617: 'Module' ステートメントは、ファイル レベルまたは名前空間レベルでしか指定できません。

        Public Message As String = "Hello, world!"

    End Module

End Module

モジュールは、ソースファイル内の最上位の要素として宣言するか、名前空間(Namespace)直下の要素として宣言する必要があります。


モジュールを入れ子にできない一方、どうしても入れ子になったモジュールが必要な場合は、代替手段としてクラスを用いることができます。 クラスはモジュールやクラス内で入れ子にして宣言できるため、モジュールの代わりに用いることができます。

例として、先に挙げたようなコードをクラスを使って宣言すると次のようになります。

クラスを使い、入れ子になったモジュールと同等の宣言を行う例
Module MainModule

    Sub Main()

        ' モジュール内で入れ子になっているクラス内の共有変数を参照する
        Console.WriteLine(SubModule.NestedClass.Message)

    End Sub

End Module

Module SubModule

    ' 入れ子になったモジュールの代替として、入れ子になったクラスを宣言する
    ' モジュールと同様、継承できないようにするために、NotInheritableとして宣言する
    NotInheritable Class NestedClass

        ' モジュールと同様、インスタンス化できないようにするために、コンストラクタをPrivateとして宣言する
        Private Sub New()
        End Sub

        ' モジュールと同様、共有メンバとするために、Sharedとして宣言する
        Public Shared Message As String = "Hello, world!"

    End Class

End Module
実行結果
Hello, world!

上記のコード中にも記載しているとおり、モジュールの代わりとしてクラスを使用する場合は、以下のようにすることでモジュールと同等の振る舞いをするように宣言します。 必須でないものもありますが、一般的なクラスと同様の使い方をして意図しない動作とならないよう、混乱を避けるための措置として推奨されます。

  • 推奨
    • NotInheritableなクラスとして宣言する (継承できないようにするため)
    • コンストラクタ(Newプロシージャ)をPrivateとして宣言する (インスタンス化できないようにする・コンストラクタの呼び出しを禁止するため)
  • 必須
    • すべてのメンバをSharedとして宣言する (共有メンバとして参照できるようにするため)

MonoのVB.NETコンパイラであるvbnc2では、入れ子になったモジュールはコンパイルエラーとはならず、宣言することができます。 入れ子になったモジュール内のメンバには、外側のモジュール名.内側のモジュール名.メンバ名 と記述することでアクセスできます。

Module MainModule

    Sub Main()

        ' 入れ子になったモジュールの変数を参照する
        Console.WriteLine(SubModule.NestedModule.Message)

    End Sub

End Module

Module SubModule

    ' 入れ子になったモジュール
    Module NestedModule

        Public Message As String = "Hello, world!"

    End Module

End Module
実行結果
Hello, world!

ただし、これはおそらくコンパイラのバグと思われます。

特殊なプロシージャ

Mainプロシージャ

Mainプロシージャは特殊なプロシージャで、プログラムの開始点となるプロシージャです。 Mainプロシージャは一つのプログラムに必ず一つ記述する必要があります。 全く記述しなかったり、二つ以上記述することはできません。 ただし、VB.NETではフォームをアプリケーションのエントリーポイントに指定した場合はMainプロシージャの記述を省略することができます。

Mainプロシージャはモジュールだけでなく、共有メソッドとしてクラスで宣言することも可能です。 Mainプロシージャの形式についてはアプリケーションの種類とコンソールアプリケーションを参照してください。

Newプロシージャ

Newプロシージャも特殊なプロシージャで、これはVB.NETから導入されました。 Newプロシージャはクラスにおけるコンストラクタと同様の働きをするもので、Newプロシージャが記述されているモジュールでは、モジュールが読み込まれた時点で自動的にこのNewプロシージャが実行されます。 すべてのモジュールはこのプロシージャを持つことができます。

次のコードでは、Newプロシージャでメッセージを初期化し、Mainプロシージャでそのメッセージを表示しています。

Module MainModule

    ' 値が設定されていないモジュール変数の宣言
    Dim message As String

    Sub New()

        ' モジュールが読み込まれた時点で自動的にNewプロシージャが呼び出される
        ' ここで変数に値を設定する
        message = "Hello, world!"

    End Sub

    Sub Main()

        ' Newプロシージャでモジュール変数messageに値が設定されているため、
        ' ここではその値が表示される
        Console.WriteLine(message)

    End Sub

End Module
実行結果
Hello, world!