プロシージャのトラブルシューティング (Visual Basic)
ここでは、プロシージャを使用する際に起きる一般的な問題についていくつか説明します。
Function プロシージャから配列型を返す
Function プロシージャが配列型を返す場合は、Function 名を使って配列の各要素に値を格納することはできません。格納しようとすると、コンパイラによって Function の呼び出しと解釈されます。たとえば、次のコードはコンパイル エラーになります。
Function allOnes(ByVal n As Integer) As Integer()
For i As Integer = 1 To n - 1
' The following statement generates a COMPILER ERROR.
allOnes(i) = 1
Next i
' The following statement generates a COMPILER ERROR.
Return allOnes()
End Function
allOnes(i) = 1 ステートメントは、間違った型の引数 (Integer 配列ではなく単一の Integer) を指定して allOnes を呼び出していると解釈されるため、ここでコンパイル エラーが発生します。Return allOnes() ステートメントはコンパイル エラーを発生します。引数を指定せずに allOnes を呼び出していると解釈されるからです。
正しい方法: 返される配列の要素を変更できるようにするには、内部配列をローカル変数として定義します。次の例はコンパイル エラーが発生しません。
Function allOnes(ByVal n As Integer) As Integer()
Dim i As Integer, iArray(n) As Integer
For i = 0 To n - 1
iArray(i) = 1
Next i
Return iArray
End Function
引数がプロシージャ呼び出しによって変更されない
呼び出しコードの引数の基になるプログラミング要素を、プロシージャで変更できるようにする場合は、参照渡しで渡す必要があります。ただし、値渡しで渡した場合でも、プロシージャはその参照型の引数の要素にアクセスできます。
基になる変数基になる変数の要素の値そのものをプロシージャで変更できるようにするには、プロシージャのパラメーターを ByRef (Visual Basic) で宣言する必要があります。また、呼び出しコードで引数をかっこで囲んで記述しないでください。こうすると、ByRef で引数を渡す機能がオーバーライドされるからです。
型参照要素パラメーターを ByVal (Visual Basic) で宣言すると、基になる変数要素そのものをプロシージャで変更できなくなります。しかし、引数が参照型であれば、プロシージャは変数の値を置き換えることはできませんが、引数が指すオブジェクトのメンバーを変更できます。たとえば、引数が配列変数であった場合、プロシージャは新しい配列を代入することはできませんが、その 1 つ以上の要素を変更できます。変更した要素は、呼び出し元のコードの基の配列変数に反映されます。
次の例には、配列変数を値渡しで受け取ってその要素を操作する 2 つのプロシージャが定義されています。increase プロシージャは、各要素に単純に 1 を加算します。replace プロシージャは、パラメーター a() に新しい配列を代入してから各要素に 1 を加算します。ただし、この新しい配列の代入は、呼び出し元のコードの基の配列変数には反映されません。なぜなら、a() が ByVal で宣言されているからです。
Public Sub increase(ByVal a() As Long)
For j As Integer = 0 To UBound(a)
a(j) = a(j) + 1
Next j
End Sub
Public Sub replace(ByVal a() As Long)
Dim k() As Long = {100, 200, 300}
a = k
For j As Integer = 0 To UBound(a)
a(j) = a(j) + 1
Next j
End Sub
次に、increase と replace を呼び出す例を示します。
Dim n() As Long = {10, 20, 30, 40}
Call increase(n)
MsgBox("After increase(n): " & CStr(n(0)) & ", " &
CStr(n(1)) & ", " & CStr(n(2)) & ", " & CStr(n(3)))
Call replace(n)
MsgBox("After replace(n): " & CStr(n(0)) & ", " &
CStr(n(1)) & ", " & CStr(n(2)) & ", " & CStr(n(3)))
最初の MsgBox の呼び出しでは、"After increase(n): 11, 21, 31, 41" と表示されます。n が参照型なので、ByVal で渡されていても increase はそのメンバーを変更できます。
2 番目の MsgBox の呼び出しでは、"After replace(n): 11, 21, 31, 41" と表示されます。n が ByVal で渡されたため、replace は変数 n を、新しい配列を代入するという方法で変更できません。replace が新しい配列インスタンス k を作成し、そこにローカル変数 a を代入すると、呼び出しコードによって渡された n への参照が失われます。a のメンバーをインクリメントすると、ローカル変数 k だけに反映されます。
正しい方法: 基になる変数要素そのものを変更可能にするには、参照渡しで渡します。replace の宣言を変更し、呼び出しコードで配列を別の配列に置き換えることができるようにする例を次に示します。
Public Sub replace(ByRef a() As Long)
オーバーロードを定義できない
プロシージャのオーバーロードを定義する場合は、名前を同じにしてシグネチャを変える必要があります。シグネチャが同じで、コンパイラがプロシージャ宣言をオーバーロードと区別できない場合は、エラーが発生します。
プロシージャのシグネチャは、プロシージャ名とパラメーター リストによって決まります。各オーバーロードは名前がすべて同じである必要がありますが、シグネチャのその他のコンポーネントについては、少なくとも 1 つが他のいずれとも同じでないことが必要です。詳細については、「プロシージャのオーバーロード (Visual Basic)」を参照してください。
次の項目は、パラメーター リストに記述されますが、プロシージャのシグネチャのコンポーネントには含まれません。
Public、Shared、Static などのプロシージャ修飾子キーワード
パラメーター名
ByRef や Optional などのパラメーター修飾子キーワード
戻り値のデータ型 (変換演算子以外)
これらの項目を変更するだけでは、プロシージャをオーバーロードすることはできません。
正しい方法: プロシージャのオーバーロードを定義するには、シグネチャを変える必要があります。名前は同じでなくてはならないため、パラメーターの数、順序、またはデータ型を変えることが必要です。ジェネリック プロシージャの場合は、型パラメーターの数を変更できます。変換演算子 (CType 関数 (Visual Basic)) の場合は、戻り値の型を変更できます。
省略可能な引数やパラメーター配列の引数を持つオーバーロードの解決
Optional (Visual Basic) パラメーターまたは ParamArray (Visual Basic) パラメーターを持つプロシージャをオーバーロードする場合は、暗黙のオーバーロードを重複させないよう注意する必要があります。詳細については、「プロシージャのオーバーロードに関する注意事項 (Visual Basic)」を参照してください。
オーバーロードされたプロシージャのうち、正しい形式を呼び出すことができない
プロシージャが複数の形式でオーバーロードされている場合は、それらすべてのパラメーター リストをよく確認することと、Visual Basic が複数のオーバーロードの中からどのように呼び出しを解決するかを理解することが必要です。そうしないと、意図した形式と異なるオーバーロードを呼び出す可能性があります。
どのオーバーロードを呼び出すか決定したら、次の規則に注意して従ってください。
正しい数の引数を、正しい順序で指定する。
最も望ましいのは、引数の型が対応するパラメーターの型とまったく同じであることです。そうでない場合でも、各引数のデータ型は対応するパラメーターの型よりも小さいことが必要です。これは Option Strict ステートメント に Off が設定されている場合でも同じです。オーバーロードが引数リストによって縮小変換する必要が生じれば、そのオーバーロードは呼び出しの対象から外されます。
拡大変換を必要とする引数を指定する場合は、対応するパラメーターにデータ型をできるだけ近づけるようにしてください。引数のデータ型を許容するオーバーロードが 2 つ以上ある場合、コンパイラは拡大変換の量が最も少ないオーバーロードに呼び出しを解決します。
引数を指定するとき、変換キーワード CType 関数 (Visual Basic) を使用すると、データ型の不一致の可能性を減らすことができます。
オーバーロードの解決エラー
オーバーロードされたプロシージャを呼び出すと、コンパイラは 1 つを除くすべてのオーバーロードの除外を試みます。うまく除外できれば、呼び出しをそのオーバーロードに解決します。すべてのオーバーロードが除外された場合、またはオーバーロードの候補を 1 つに絞ることができない場合は、エラーが発生します。
次の例は、オーバーロード解決のプロセスを示しています。
Overloads Sub z(ByVal x As Byte, ByVal y As Double)
End Sub
Overloads Sub z(ByVal x As Short, ByVal y As Single)
End Sub
Overloads Sub z(ByVal x As Integer, ByVal y As Single)
End Sub
Dim r, s As Short
Call z(r, s)
Dim p As Byte, q As Short
' The following statement causes an overload resolution error.
Call z(p, q)
1 回目の呼び出しでは、コンパイラは最初のオーバーロードを除外します。1 つ目の引数の型 (Short) が、対応するパラメーターの型 (Byte) よりも大きいからです。次に、3 番目のオーバーロードを除去します。2 番目のオーバーロードの各引数型 (Short と Single) よりも、3 番目のオーバーロードで対応する引数型 (Integer と Single) の方が大きいからです。2 番目のオーバーロードの方が拡大変換が少なくて済むため、コンパイラは 2 番目を使用して呼び出します。
2 回目の呼び出しでは、コンパイラは引数の型が大きすぎるという理由でオーバーロードを除外できません。3 番目のオーバーロードは、1 回目の呼び出しのときと同じ理由で除外されます。2 番目のオーバーロードを呼び出した方が引数の型の拡大変換が少なくて済むからです。しかし、コンパイラは 1 番目と 2 番目のオーバーロードのいずれかに解決できません。どちらにも、対応する型に拡大変換されるパラメーター型が 1 つ定義されています (Byte から Short、そして Single から Double)。このため、オーバーロード解決エラーが生成されます。
正しい方法: オーバーロードされたプロシージャを明確に呼び出すには、CType 関数 (Visual Basic) を使用して引数のデータ型をパラメーターの型に一致させます。次の例で呼び出される z は、必ず 2 番目のオーバーロードに解決します。
Call z(CType(p, Short), CType(q, Single))
省略可能な引数やパラメーター配列の引数を持つオーバーロードの解決
プロシージャの 2 つのオーバーロードのシグネチャが同じで、唯一の違いが最後のパラメーターの宣言 (一方が Optional (Visual Basic) で、他方は ParamArray (Visual Basic)) である場合、コンパイラはそのプロシージャの呼び出しを、より厳密に一致している方に解決します。詳細については、「オーバーロードの解決法 (Visual Basic)」を参照してください。
参照
概念
Function プロシージャ (Visual Basic)
Property プロシージャ (Visual Basic)
プロシージャのパラメーターと引数 (Visual Basic)