プロシージャとはC/C++言語で言う関数のことです。 プロシージャには「手続き」という意味があり、VBでは一連の処理をまとめたものという意味合いで使われます。

VBではモジュールに属するものは特にプロシージャと呼ばれ、一方クラス構造体に属するものはメソッドと呼ばれ、両者は区別される事が多いようです。 なお、値を返さないSubプロシージャはサブルーチン、値を返すFunctionプロシージャは関数(ファンクション)という用語で区別されることもあるようです。 VBのキーワードSubFunctionはこれに由来します。

プロシージャにはよく使われる一連の処理の流れを記述します。 これにより何度も同じコードを書く手間を減らすことができます。

VB.NETでのプロシージャとVB6からの変更点

VB.NETのプロシージャには値を返さないSubプロシージャと、値を返すFunctionプロシージャが存在します。 両者の違いは値を返すかどうかだけです。 また、プロパティ構文を成すプロシージャをプロパティプロシージャと言います。

プロシージャは値を返すか否かに関わらず、任意の数・任意の型の引数を取ることができます。 引数はプロシージャに対して渡す情報ととらえることができます。

プロシージャの宣言方法はVB6以前とほとんど変わりありませんが、一部変更になった部分もあります。 まず、その変更点をまとめておきます(すべてVB.NETでの仕様です)。

  • 引数の渡し方は既定でByVal(値渡し)となる
  • ByRef(参照渡し)で渡されたプロパティ引数は、プロパティプロシージャ内での変更が反映される
  • Subプロシージャを呼び出す場合、常に引数を括弧でくくる必要がある
  • Returnキーワードを用いてSubプロシージャの呼び出し元に戻ることができる
  • Returnキーワードを用いてFunctionプロシージャの戻り値を返すことができる
  • Static修飾子を指定してプロシージャを宣言することはできない

宣言・呼び出し

Subプロシージャ、Functionプロシージャの宣言の例をまとめておきます。

プロシージャの宣言の例
' 引数なし、戻り値なしのプロシージャ
Sub Procedure1()
End Sub

' 引数にInteger型の値を一つ取り、戻り値なしのプロシージャ
Sub Procedure2(ByVal arg As Integer)
End Sub

' Integer型の値を参照渡し、Double型の値を値渡しで受け、戻り値なしのプロシージャ
Sub Procedure3(ByRef arg1 As Integer, ByVal arg2 As Double)
End Sub

' 引数なし、戻り値にInteger型の値を返すプロシージャ
Function Procedure4() As Integer
  Return 0
End Function

' 引数にString型の値を取り、戻り値にInteger型の値を返すプロシージャ
Function Procedure5(ByVal arg As String) As Integer
  Return 0
End Function

これらのプロシージャを呼び出す場合は次のようにします。 VB.NETでは引き続きCallでプロシージャを呼び出すこともできますが、一般的にはあまり使われていないようです。 CallでFunctionプロシージャを呼び出した場合は、戻り値は特に代入されず破棄されます。

Procedure1()

Procedure2(15)

Call Procedure3(2, 148.0)

Procedure4()

Call Procedure5("Test")

戻り値の返却・受け取り

Functionプロシージャでの戻り値の返し方には2パターンあります。 1つはReturnで戻り値を返すパターン、もう1つはプロシージャ名に戻り値を代入するパターンです。

Returnを用いた場合はその時点でプロシージャの処理が完了します。 逆に、プロシージャ名に戻り値を代入する場合はEnd Functionまで処理は継続されます。

Functionプロシージャでの戻り値の返し方
Function FunctionProcedure1() As Integer
  Return 0

  ' これ以降の処理は実行されない
End Function

Function FunctionProcedure2() As Integer
  FunctionProcedure2 = 10

  ' これ以降の処理も実行される
End Function

Functionプロシージャの戻り値を受け取るには、戻り値の型と同じ型の変数を用意し、その変数に代入するようにします。

Functionプロシージャの戻り値の受け取り方
Sub Main()
  Dim returnValue As Integer

  ' 戻り値の受け取り
  returnValue = GetRandomValue()

  Console.WriteLine("戻り値: {0}", returnValue)
End Sub

Function GetRandomValue() As Integer
  ' 10未満のランダムな数値を返す
  Return CInt(Rnd() * 10)
End Function

また、式の途中にFunctionプロシージャの呼び出しを含めることで、その戻り値を直接使用することもできます。

Sub Main()
  Dim returnValue As Integer

  ' 戻り値を10倍する事で 0, 10, 20 .... 90 のランダムな数値を取得する
  returnValue = 10 * GetRandomValue()

  Console.WriteLine("戻り値: {0}", returnValue)
End Sub

Function GetRandomValue() As Integer
  ' 10未満のランダムな数値を返す
  Return CInt(Rnd() * 10)
End Function

プロシージャの中断

ReturnおよびExitステートメント(Exit SubまたはExit Function)を使用することでプロシージャの処理を中断することができます。 FunctionプロシージャではReturnは戻り値を返すために使用されますが、戻り値を返すと同時にそれ以降の処理も中断します。

プロシージャ内での処理の中断
Sub SubProcedure()
  Dim i As Integer

  For i = 0 To 10000
    ' 5000までループしたら処理を中断します
    If i = 5000 Then Exit Sub

    ' 上のExit Subの場合と全く同じ動作をします
    If i = 5000 Then Return
  Next
End Sub

Function FunctionProcedure() As Integer
  Dim i As Integer

  For i = 0 To 10000
    ' 5000までループしたら処理を中断します
    If i = 5000 Then Exit Function

    ' 上のExit Functionの場合と同じ動作をしますが、
    ' Functionプロシージャ内でReturnを使用する場合は戻り値を返す必要があります
    If i = 5000 Then Return i
  Next
End Function

ExitステートメントでFunctionプロシージャの処理を中断した場合、プロシージャ名に戻り値を代入している場合はその値、代入していない場合はデフォルトの値(参照型ではNothing、値型では0など)が戻り値として返されます。

参照型・値型のデフォルト値については型の種類・サイズ・精度・値域 §.型のデフォルト値を参照してください。

ByValとByRef

引数の渡し方には値渡し参照渡しの二種類があります。 引数リストの宣言でByValを指定すると値渡し、ByRefを指定すると参照渡しで変数の値が渡されます。

値渡しでは引数に値のコピーが渡されるため、プロシージャの中で変更を加えても元の変数の値が変わることはありません。 逆に参照渡しでは、変数に格納されている値への参照を渡すため、プロシージャの中での変更は元の変数にも影響します

次のコードは値渡しと参照渡しでの値の変化を比較するためのものです。

値渡しと参照渡し
Sub Main()
  Dim val As Integer

  ' 値渡し
  val = 7
  ByValProcedure(val)

  Console.WriteLine("ByVal Value: {0}", val)

  ' 参照渡し
  val = 7
  ByRefProcedure(val)

  Console.WriteLine("ByRef Value: {0}", val)
End Sub

' 値渡しで引数を受け取るプロシージャ
Sub ByValProcedure(ByVal arg As Integer)
  arg = 15
End Sub

' 参照渡しで引数を受け取るプロシージャ
Sub ByRefProcedure(ByRef arg As Integer)
  arg = 15
End Sub
実行結果
ByVal Value: 7
ByRef Value: 15

参照渡しのパラメータでも元の変数の値を変更させたくない場合は、次のように値を括弧( )でくくって渡すようにします。

Sub Main()
  Dim val As Integer

  ' 参照渡しでも変数の値を変更されないようにする
  val = 7
  ByRefProcedure((val))

  Console.WriteLine("ByRef Value: {0}", val)
End Sub

' 参照渡しで引数を受け取るプロシージャ
Sub ByRefProcedure(ByRef arg As Integer)
  arg = 15
End Sub
実行結果
ByRef Value: 7

なお、値のコピーとインスタンスのコピーを混同しないようにしてください。 値型では値のコピーとインスタンスのコピーは同じですが、参照型では値のコピーとは同じインスタンスを参照するようにすることで、インスタンスのコピーとは1つのインスタンスからその複製を作ることです。 参照型の値渡し(ByVal)で渡されるのはどのインスタンスを参照するかという情報のコピーであり、参照渡し(ByRef)で変化するのは変数が参照するインスタンスです。

値型と参照型の違いについてはクラスで解説しています。

省略可能な引数

VB.NETではプロシージャの引数を省略可能にすることができます。 Optionalキーワードを付けることで引数を省略可能とすることができますが、Optionalをつけた引数より後ろにある引数にもOptionalを付ける必要があります。

VB6以前でも省略可能な引数を取ることができましたが、VB.NETでは省略可能な引数には必ず既定値を定めなければなりません。 そのため、引数が設定されているかを調べるIsMissing関数などはVB.NETには存在しません。

次の例は省略可能な引数リストを持つプロシージャとそれを呼び出した例です。

省略可能な引数の例
' 引数のうちwは必須、x, y, zは省略可能とする
' 省略された場合の初期値はそれぞれx = 2, y = 7, z = 15とする
Sub OptionalProcedure(ByVal w As Integer, Optional ByVal x As Integer = 2, Optional ByVal y As Integer = 7, Optional ByVal z As Integer = 15)
  Console.WriteLine("w:{0}, x:{1}, y:{2}, z:{3}", w, x, y, z)
End Sub

Sub Main()
  ' すべて指定
  OptionalProcedure(4, 3, 2, 1)

  ' x を省略
  OptionalProcedure(10, , 20, 30)

  ' y, z を省略
  OptionalProcedure(5, 10, , )

  ' x, y, z をすべて省略
  OptionalProcedure(0, , , )
End Sub
実行結果
w:4, x:3, y:2, z:1
w:10, x:2, y:20, z:30
w:5, x:10, y:7, z:15
w:0, x:2, y:7, z:15

可変長の引数

VB.NETではプロシージャに任意の数の値をパラメータとして渡すこともできます。 渡される引数の数がわからないときや任意の数を指定することを許可する場合には、ParamArrayを指定することで引数を可変長にすることができます。 なお、ParamArrayを指定する可変長引数はすべて同じ型でなければならないため、引数を配列として受け取るようにする必要があります。

可変長引数を取るプロシージャを呼び出す際、任意個の値を指定できるだけでなく、全く値を指定しないで呼び出すこともできます。

可変長引数を取るプロシージャの例
' 任意個のInteger型の値を引数にとるプロシージャ
Sub ParamArrayProcedure(ByVal ParamArray values() As Integer)
  ' 渡された引数の数を表示
  Console.Write("{0}個の引数: ", values.Length)

  ' 渡された引数をすべて列挙
  For Each value As Integer In values
    Console.Write("{0}, ", value)
  Next

  Console.WriteLine()
End Sub

Sub Main()
  ' 3つ指定
  ParamArrayProcedure(2, 3, 5)

  ' 5つ指定
  ParamArrayProcedure(1, 3, 5, 7, 9)

  ' 引数の指定を省略
  ParamArrayProcedure()
End Sub
実行結果
3個の引数: 2, 3, 5, 
5個の引数: 1, 3, 5, 7, 9, 
0個の引数: 

オーバーロード

VB.NETからはプロシージャをオーバーロードすることができるようになりました。 オーバーロードとは、同じ名前で引数リストだけが異なるプロシージャを作成することです。

たとえば、いくつかの数値型の絶対値を求めるためのプロシージャを自作するとなると、VB6では次のようなコードを書く必要がありました。

' Integer型のためのプロシージャ
Function AbsInt(val As Integer) As Integer
  If 0 <= val Then
    ' 正の時は値をそのまま返す
    AbsInt = val
  Else
    ' 負の時は符号を反転した値を返す
    AbsInt = -1 * val
  End If
End Function

' Single型のためのプロシージャ
Function AbsSng(val As Single) As Single
  ' AbsIntと同様の実装
End Function


' Double型のためのプロシージャ
Function AbsDbl(val As Double) As Double
  ' AbsIntと同様の実装
End Function

さらに、実際にこれを呼び出す場合は、変数の型を考慮しながら呼び出すプロシージャを選択しなければなりませんでした。 しかし、VB.NETではプロシージャをオーバーロードすることで、この問題を回避することができます。

たとえば、この絶対値を求めるプロシージャの一群をAbsという一つのプロシージャとしてオーバーロードすると次のようになります。

プロシージャのオーバーロードの例
' Integer型用のプロシージャ
Function Abs(ByVal val As Integer) As Integer
  If 0 <= val Then
    ' 正の時は値をそのまま返す
    Return val
  Else
    ' 負の時は符号を反転した値を返す
    Return -1 * val
  End If
End Function

' Single型用のプロシージャ
Function Abs(ByVal val As Single) As Single
  If 0.0! <= val Then
    Return val
  Else
    Return -1.0! * val
  End If
End Function

' Double型用のプロシージャ
Function Abs(ByVal val As Double) As Double
  If 0.0# <= val Then
    Return val
  Else
    Return -1.0# * val
  End If
End Function

これらのプロシージャを呼び出す場合は普通のプロシージャの呼び出しと何ら変わりありません。 渡されたパラメータの型に合わせて、最適なものを呼び出すようコンパイラが自動的に判断します。

Sub Main()
  ' Integer のバージョンが呼び出されます
  Console.WriteLine(Abs(-3))

  ' Single のバージョンが呼び出されます
  Console.WriteLine(Abs(5.0!))

  ' Double のバージョンが呼び出されます
  Console.WriteLine(Abs(-3.14#))

  ' Double のバージョンが呼び出されます
  ' なぜならこの数値はDoubleとして扱われるからです
  Console.WriteLine(Abs(2.71828))
End Sub

幸い、この例のようにすべての型についてAbsプロシージャを定義しなくても、すでにMath.Absメソッドが提供されているのでこれを使うことができます。

オーバーロードに際して、そのプロシージャがオーバーロードされていることを明示的に示す目的でOverloadsキーワードを付加することができます。 一つのプロシージャにOverloadsキーワードを付加した場合、ほかのオーバーロードされるべきプロシージャにもこのキーワードをつけなければなりません。

Overloads Function Abs(ByVal val As Integer) As Integer
End Function

Overloads Function Abs(ByVal val As Single) As Single
End Function

Overloads Function Abs(ByVal val As Double) As Double
End Function

プロシージャをオーバーロードする場合、すべてのプロシージャ名は同じでなければなりません。 また、オーバーロードする場合は引数の数・順序・型のいずれか一つが他と異なっている必要があります。 つまり戻り値の型や引数の名前の違いだけではオーバーロードできません。 ちなみに、プロシージャの名前と引数の数・順序・型の要素をまとめてシグネチャ(もしくはシグニチャ、signature)といいます。

引数の数・順序・型が同じで、戻り値の型だけがことなるプロシージャを複数作成したい場合は、名前を変えて別名のプロシージャとして作成する必要があります。 なお、そういったプロシージャではオーバーロードではなくジェネリクスを使った実装が適している場合もあります。

外部DLL、Win32 APIの呼び出し

VB6以前では、Declareステートメントを使って外部DLLやWin32 APIを呼び出すためのプロシージャを宣言することが出来ました。 VB.NETでも引き続きDeclareステートメントを使うことが出来ますが、これとは別にDllImport属性を使うことでも外部DLLの関数を呼び出すためのプロシージャを宣言することが出来ます。 次の例では、いずれもWin32 APIのLockWorkStation関数を呼び出すためのプロシージャを宣言しています。

Declareステートメントを使った例
Private Declare Function LockWorkStation Lib "user32" () As Boolean
DllImport属性を使った例
<DllImport("user32.dll")> _
Private Shared Function LockWorkStation() As Boolean
End Function

DllImport属性や外部DLLの呼び出しとプロシージャの宣言については詳しく解説しないので、必要に応じて他のドキュメントや属性とメタデータなどを参照してください。

その他のプロシージャ

VB.NETではSubFunction以外にも特別な役割を与えられるプロシージャが存在します。 例えば、プロパティ構文となるプロパティプロシージャや、演算子のオーバーロードを実現する演算子プロシージャなどです。 これらのプロシージャの構文や使い方・実装方法についてはプロパティ演算子のオーバーロードを参照してください。