3-6 基本的なメソッド定義と呼び出し

3-6-1 基本的なメソッドの構文

「メソッド」 は、一連の手続き(処理の流れ)を記述するブロックで、「プロシージャ」、または 「関数」 などともよばれています。この章で扱っている WinApp2 プロジェクトの Form1.vb にあるイベントハンドラ、Button1_Click や Button2_Click もメソッドです。つまり、Sub~End Sub のブロックは、メソッドを記述する 1 つの方法です。

VB .NET では、メソッドを書くべき場所は Class ブロックの中か、または Structure ブロック、Module ブロックなどの内部です。これらのブロックに属さず、ダイレクトにソースファイル内にメソッドを記述すると、コンパイルエラーになります。

Class などのブロック内に記述するとき、複数のメソッドを記述することができます。WinApp2 プロジェクトも、Class Form1~End Class のブロック内に、Button1_Click と Button2_Click の 2 つのメソッドがすでにあります。

1 本のプログラムを作成するとき、すべての処理を 1 つのメソッドに記述するのではなく、複数のメソッドに分割することで、可読性や再利用性も高まります(もっとも、一般的に VB .NET などのオブジェクト指向言語におけるソースコードの再利用の単位はクラス(Class~End Class で構成されるブロック)ですが、大きな手続きを 1 つのメソッドにまとめるよりは、細かいメソッドに分割したほうがメソッドの流用性は高まります)。

クラスについての説明は、第 5 章 「クラスの定義と実装」 まで待つことにして、ここでは、既存の WinApp2 プロジェクトの Class Form1~End Class という Class ブロックを使って、メソッドそのものの基本的な構文について確認します。

以下の例では、WinApp2 プロジェクトの Form1.vb の Class Form1~End Class のブロック内に、さらに 2 つのメソッド A と B を加えたものです(一部説明に無関係なものは省略)。

名前 A というメソッドの定義が 15 行目 ~ 19 行目、B というメソッドが 21 行目 ~ 26 行目です。

[例] メソッド A と B

                  
 1: Public Class Form1
 2:     Inherits System.Windows.Forms.Form
 3:
 4:     Private Sub Button1_Click(ByVal sender As System.Object, ...
 5:         A()      'メソッド A を呼び出す(実行する)
 6:         B()      'メソッド A を呼び出す(実行する)
 7:     End Sub
 8:
 9:     Private Sub Button2_Click(ByVal sender As System.Object, ...
10:         Dim X As Long
11:         A()      'メソッド A を呼び出す(実行する)
12:         X = B()  'メソッド B を呼び出し、戻り値を受け取る
13:     End Sub
14:
15:     Private Sub A()
16:
17:         'A メソッドの処理内容
18:
19:     End Sub
20:
21:     Private Function B() As Integer
22:
23:         'B メソッドの処理内容
24:
25:         Return 100  '呼び出し元に値を返す
26:     End Function
27:
28: End Class

                

5 行目や 6 行目は、メソッドを実行する記述です。メソッドを実行することを、「呼び出す」 といいます。すなわち、5 行目の 「A()」 や 「B()」 という記述は、それぞれ該当するメソッドを呼び出す記述です。結局、button1 ボタンのクリックによって、4 行目から始まるイベントハンドラが呼び出されると、メソッド A とメソッド B が順番に呼び出されることになります。

呼び出されるメソッド B では、メソッド B 内で処理をおこなったのち、何らかの処理結果を呼び出し元に返すことができます。25 行目の 「Return 100」 は、100 という値を呼び出し元に返しています。12 行目のように 「X = B()」 と記述すると、B メソッドを実行したあと、25 行目で指定した 100 という値は、呼び出し元の 12 行目にある変数 X に代入されます。このように、呼ばれた側のメソッドが呼び出し側に何らかの処理結果を返すとき、その返される値のことを 「戻り値(Returned Value)」、戻すことを 「戻り値(Returned Value)を返す」 といいます。

前記の例にある A メソッドと B メソッドの定義を一般的な形式にすると、以下のようになります。Visual Basic .NET のメソッド定義は、2 つのパターンがあります。

修飾句 Sub メソッド名(引数リスト)

End Sub

修飾句 Function メソッド名(引数リスト) As 戻り値の型

    (Return ステートメントで戻り値を返せる)
End Sub

このうち、修飾句にあたる部分が Private ですが、この意味を理解するためには、第 5 章 「クラスの定義と実装」 で扱う 「クラス」 の登場を待たなければなりません。そのため、第 5 章 「クラスの定義と実装」 に入るまでの間、本書では約束事として、メソッドには 「Private」 をつけることにします。

また、「戻り値」 と 「引数リスト」 については、次項以降で扱います。

なお、前述のプログラムの 5 行目や 6 行目でのメソッドを呼び出している箇所では、単にメソッド名を書いて、その後ろに丸括弧( )を書いていますが、いままでサンプルに登場したいくつかのメソッド呼び出しでは、Convert.ToDecimal() や String.Format() というように、ToDecimal メソッドや Format メソッドより先に、クラス名とドットをつけて修飾して記述しました。このようなドットを使ってクラスを特定する方法は、第 5 章 「クラスの定義と実装」 で詳しく説明します。

同一のクラスブロック内において、Button1_Click や Button2_Click から A や B を呼び出す分には、ドットをつけてメソッド名を修飾する必要はありません。しばらくの間は、同じクラスブロック内において、単にメソッド名を書いて呼び出す方法をおこないます。

3-6-2 メソッドの引数

VB .NET でもほかのプログラミング言語と同様に、メソッドを呼び出すとき、メソッドにデータを与え(つまり引数を渡し)、そのデータをメソッドに処理させ、処理結果(つまり戻り値)をもらうことができます。

メソッドに与えるデータである引数や、処理結果である戻り値を扱うためには、どんな引数や戻り値を使うか、メソッドを定義する際にあらかじめ宣言しておく必要があります。戻り値は、1 つのメソッドに最大 1 つであり、メソッドの引数は 0 個以上、複数もつことができます。

VB .NET では、メソッドを定義する際、戻り値を扱うか否かによって、メソッド本体の記述が異なります。戻り値を扱わないメソッドは、Sub~End Sub のブロックを使用し、戻り値を扱う場合は、Function~End Function のブロックを使います。ただし、引数の書き方については Sub を使う場合も、Function を使う場合も変わりません(次に、Sub ブロックで構成されるメソッドを使って引数の説明をしますが、この説明は Function ブロックにも適用できます)。

メソッドで引数を扱うためには、メソッド定義の際、メソッド名の後ろの丸括弧( )の中に、次のような引数リストを定義します。

(引数リストの定義)
    修飾句 引数名 As 引数型, 修飾句 引数名 As 引数型, ...

1 つの引数定義が 「修飾句 引数名 As 引数型」 であり、複数の引数をもつ場合は、カンマで区切って引数定義を並べます。修飾句には、ByVal、ByRef、Optional、ParamArray があります。省略した場合は、ByVal がついたものとみなされます(Optional や ParamArray は、第 5 章 「クラスの定義と実装」 で改めて扱います。ByVal と ByRef の違いは、3-6-4 「ByVal と ByRef」 で扱います)。

以下は、Integer 型の引数が 2 つあるメソッドの例です(前項でも説明したように、メソッドの修飾句は、しばらくの間 Private をつけておきます。このコードは、WinApp2 プロジェクト Form1.vb にある Form1 クラス内に記述することを前提にしています。Button1_Click イベントハンドラにも、Add メソッドの呼び出しを追加しています)。

[例] Add メソッドの呼び出しと、そのメソッド定義

                  
 1: Private Sub Button1_Click(ByVal sender As System.Object, ...
 2:     Dim N1 As Integer = 100, N2 As Integer = 200
 3:     Add(N1, N2)
 4: End Sub
 5:
 6: Private Sub Add(ByVal X As Integer, ByVal Y As Integer)
 7:     Dim W As Integer
 8:     W = X + Y
 9:     MsgBox(W)
10: End Sub

                

この例では、6 行目において、2 つの引数 X、Y が宣言されています(厳密には 「仮引数」 といいます)。この引数 X、Y は、メソッド Add にとってローカルな変数として機能します。メソッド内であれば、X と Y は自由に利用できます。

この Add メソッドを呼び出すときは、同じ数の引数を渡す必要があります(厳密には、渡すほうの引数を 「実引数」 といいます)。2 つ以上の引数がある場合、この順番にメソッドに渡されます。3 行目では Add メソッドを呼び出しており、変数 N1 と変数 N2 の値が、6 行目のメソッドの引数に渡ってきます。引数を記述した順番に渡るので、それぞれ、変数 N1 の値は引数 X に代入され、変数 N2 の値は引数 Y に代入されることになります。この例では、N1 は 100、N2 は 200 なので、引数 X は 100、引数 Y は 200 になります。結果として、8 行目で計算結果 300 が求められ、9 行目でメッセージボックスに 300 という値を表示します。

また、この例では、メソッドを呼び出す側(3 行目)を呼ばれる側(6 行目)よりも先に記述しましたが、呼ばれるメソッドと呼ぶ側の箇所は、ソースコード上どちらを先に書かなければならないというルールはありません。この例でいえば、Button1_Click イベントハンドラよりも、Add メソッドのほうを先に記述しても問題なく動作します。

なお、引数を渡すほうの変数名(実引数)と、受ける側のメソッドの引数名(仮引数)をあわせる必要はありません。メソッドを呼び出す際に、引数としてリテラルや計算式を渡すこともできます。

[例] メソッド呼び出しの際、リテラルや計算式を渡す

                  
3: Add( 250, N1 + 30 )

                

この場合、Add メソッドでは引数 X に 250、引数 Y には N1 + 30 の計算結果が渡ります。

また、呼び出し元から引数を渡す方法として 「:=」 という記号を使い、メソッドのどの仮引数に値を渡すか指定することもできます。

[例] メソッド呼び出しの際、どの仮引数(Y か X)に渡すか指定する

                  
3: Add( Y: = N1, X := N2 )

                

これの書式を 「名前付き引数」 とよびます。この方法では、実引数を並べた順番に関係なく、この例では N1 の値は引数 Y に、N2 の値は引数 X に渡ります。

ワンポイント  

● for VB6

VB6 では、修飾句として ByVal も ByRef もつけない場合、既定では ByRef とみなされましたが、VB .NET では、既定が ByVal です。


3-6-3 戻り値

VB .NET のメソッドの定義において、戻り値を扱うのであれば、Sub キーワードでメソッドを定義するのではなく、Function キーワードを使わなければなりません。Sub ~ End Sub ブロックを使ったメソッド定義では戻り値を返すことができません。メソッドが戻り値を返すようにするには、以下のように記述します。

修飾句 Function メソッド名(引数リスト) As 戻り値の型

End Function

そのメソッドが戻り値を返すようにするには、どのような戻り値を返すか、データ型を As 句の後ろに記述します(もし、「As データ型」 の記述を省略した場合は、暗黙的に 「As Object」 とみなされますが、Option Strict がオンの場合は、「As データ型」 を省略するとコンパイルエラーになります。Option Strict については、3-1-5 「Dim 文によるさまざまな変数の宣言」 の 「参考」 欄を参照)。

以下のプログラムは、前項で扱った Add メソッドが戻り値を返すように修正したものです。なお、8 行目と9 行目は、本来 1 行で書いて構いません。ここでは、8 行目が長くなるので、継続行記号(アンダーバー)をつけて 9 行目に続きを記述しました。

                  
 1: Private Sub Button1_Click(ByVal sender As System.Object, ...
 2:     Dim N1 As Integer = 100, N2 As Integer = 200
 3:     Dim Ans As Integer
 4:     Ans = Add(N1, N2)
 5:     MsgBox(Ans)
 6: End Sub
 7:
 8: Private Function Add(ByVal X As Integer, ByVal Y As Integer) _
 9:                                                  As Integer
10:     Dim W As Integer
11:     W = X + Y
12:     Return W
13: End Function

                

8 行目の Add メソッドは Function キーワードを使っており、続けて戻り値のデータ型を示す 「As Integer」 句がついています。また、戻り値を返している箇所は 12 行目で、ここでは、X+Y の計算結果である W の値を返しています。Return ステートメントの後ろには、返すべき値を書きます。ここでは、W という変数を記述しましたが、Return ステートメントの後ろには、リテラルや計算式も書くことができます。

この Return ステートメントの値を受け取るのが 4 行目の記述であり、戻り値は、変数 Ans に格納されます。

なお、Return ステートメントには戻り値を呼び出し元に伝える機能のほかに、その時点でメソッドを抜けて、そのメソッドを終了する機能があります。つまり、メソッドの途中で Return ステートメントを記述すると、その時点でメソッドの処理は終わり、呼び出し元に制御が戻ります。そのため、ある条件を満たしたときはメソッドの処理を途中で終え、呼び出し元に戻るという柔軟な制御を書くことができます。

また、Return ステートメントの後ろに戻り値を指定しない書き方もあり、その場合、Sub~End Sub ブロックのメソッドでも Return ステートメントを使うことができます。戻り値を指定しない Return ステートメントは、単にメソッドの処理を終了して呼び出し元に戻るという意味です。その他、メソッドの処理途中で強制的に処理を止めて呼び出し元に制御を戻す方法として、Function には 「Exit Function」、Sub には 「Exit Sub」 というステートメントがあります。これらの一連の制御構造については、第 4 章 「さまざまな制御構造」 で改めて扱います。

VB .NET には、戻り値を返す従来からの方法も書くことができます。次の例のように、「メソッド名 = 戻り値」 と記述すると、戻り値を設定できます。ただし、この記述だけではメソッドの処理は終了しません。メソッドの処理を終了するには、メソッドの終わりに到達するか、Exit Function ステートメントを使う必要があります。

[例] もう 1 つの戻り値の設定方法

                  
 8: Private Function Add(ByVal X As Integer, ByVal Y As Integer) _
 9:                                                  As Integer
10:     Dim W As Integer
11:     W = X + Y
12:     Add = W  '戻り値を設定する
13: End Function

                

3-6-4 ByVal と ByRef

引数の修飾子の中で ByVal と ByRef は、引数の渡し方を決めるものです。どちらを使っても、データを引数としてメソッドに渡すことができますが、そのデータの扱い方が若干違います。

以下の例を使って説明します(いままで同様、このコードは WinApp2 プロジェクトの Form1 クラスのブロック内に記述してあることを想定しています)。

[例] ByVal と ByRef の違い

                  
 1: Private Sub Button1_Click(ByVal sender As System.Object, ...
 2:     Dim N1 As Integer = 100, N2 As Integer = 100
 3:     F(N1)
 4:     MsgBox(N1) 'もともと 100 であった N1 の値は?
 5:     G(N2)
 6:     MsgBox(N2) 'もともと 100 であった N2 の値は?
 7: End Sub
 8:
 9: Private Sub F(ByVal X As Integer)
10:     X = 10
11: End Sub
12:
13: Private Sub G(ByRef X As Integer)
14:     X = 10
15: End Sub

                

この例では、9 行目にFメソッド、13 行目に G メソッドが定義してあります。この 2 つのメソッドの違いは、引数の修飾子が ByVal か ByRef かの違いです。それぞれのメソッドは 3 行目、5 行目から呼び出されています。

呼び出し元のほうには、変数 N1(値は 100)と変数 N2(値は 100)があります。それぞれのメソッド呼び出しによって、N1 の値は 3 行目から 9 行目のFメソッドの引数 X に渡り、N2 の値は 5 行目から 13 行目の G メソッドに渡ることになります。2 つの F、G メソッドでは、どちらも受け取ったデータをもっているはずの仮引数 X に 10 を代入して、値を上書きしています(10 行目と14 行目)。さて、このとき呼び出し元の変数 N1 と N2 の値は 10 に変わるのでしょうか。実は、修飾子が ByVal か ByRef かによって、呼び出し元への影響が異なります。

ByVal をつけたほうは、変数 N1 から仮引数 X への値のコピーをおこなっています。そのためコピー先の仮引数 X の値を変更しても、オリジナルの N1 はいっさい影響を受けません。見方を変えると、変数 N1 と仮引数 X は、別々のメモリ領域ということになります。メソッド呼び出しによって、変数 N1 の領域から、仮引数 X という別のメモリ領域に値をコピーしています。

一方、ByRef をつけたほうは、変数 N2 と仮引数 X は一身同体を意味し、変数 N2 と仮引数 X とは、同じメモリ領域を意味します。つまり、メモリ領域 N2 に、X という別の名前がついたといえます。そのため、仮引数 X の値を呼び出し先で変更すると、同時に変数 N2 の値も変わってしまいます。このしくみを使えば、呼び出されたメソッドのほうで、呼び出し元の変数の値を直接加工することができます。

よって、前述のサンプルでは、4 行目の時点で変数 N1 の値は 100 のままですが、6 行目の時点で、変数 N2 の値はすでに 10 に変更されています。

なお、ByVal をつけた引数を 「値渡し」、ByRef をつけた引数を 「参照渡し」 ともよびます。データ型における 「値型」 や 「参照型」 とは混同しないようにしてください。引数における値渡し(ByVal)や参照渡し(ByRef)は、メソッド呼び出しにおけるデータの伝え方の名称であり、データ型における値型や参照型とは異なる事柄です。ByVal をつけて値渡しをする引数が、値型のこともあれば、参照型のこともあります。また、ByRef をつけて参照渡しをする引数が、値型のこともあれば、参照型のこともあります。

では次に、前述のサンプルの 5 行目において、ByRef 修飾子付きの引数をもつ G メソッドを呼び出すとき、以下のように計算式を引数に渡したらどうなるでしょう。

[例] 計算式を渡す

                  
5:     G( N1 + 1 )
6:     G( (N1 + 1) * 5 )

                

呼ばれた側の G メソッドでは、引数を 10 に変更しています。この場合、計算結果の領域と仮引数 X の領域が一身同体であるといえるので、いくら仮引数 X を変更しても、計算式に使われている変数 N1 の値を変更させることはできません(結局、ByRef をつけた参照渡しにする必要性はありませんが…)。

また、以下のように、N2 変数自身にさらに丸括弧( )をつけた場合も、内側の丸括弧は計算式の優先順位を表す括弧であり、(N2) は計算式とみなされます。そのため、計算結果である N2 の値をもつ領域と仮引数 X が一身同体になるので、N2 自身の値を変えることはできません。

[例] 参照渡し(ByRef)でも、N2 の値は変わらない

                  
5:     G( (N2) )

                

つまり、見方を変えると、ByRef をつけた参照渡しの引数に対して 「N2」 と表記して渡すと、この変数 N2 の値を呼び出されたメソッド側で変えることができます。しかし、(N2) と表記して渡すと、呼び出されたメソッド側では、変数 N2 の値を直接変更することができなくなります。

[例] 参照渡しの影響が異なる

                  
5:     G( N2 )   '変数 N2 の値は変更される
6:     G((N2))   '変数 N2 の値は変更されない

                

3-6-5 値型の引数と参照型の引数

前項では、引数の渡し方の形態である値渡し(ByVal)と参照渡し(ByRef)の違いを説明しました。また同時に、引数の渡し方が値渡しや参照渡しであることと、引数のデータ型が値型や参照型であることとは直接的な関連性はなく、別の事項であることに触れました。

ここでは、引数のデータ型が 「値型」 か 「参照型」 かの違いによって、メソッド呼び出しにおいてどんな影響があるかを改めて説明します。

以下の例では、7 行目に 2 つの引数をもつメソッド F2 があります。2 つの引数は、どちらも値渡しを意味する ByVal 修飾子がついていますが、引数自体のデータ型は、一方は値型である Integer 型、もう一方は参照型である配列を使っています(このサンプルコードも、WinApp2 プロジェクトの Form1 クラスブロック内に記述することを想定しています)。

[例] 値型引数 A と参照型引数 Dat

                  
 1: Private Sub Button1_Click(ByVal sender As System.Object, ...
 2:     Dim X As Integer = 1000
 3:     Dim MyArr() As Integer = {700, 800, 900}
 4:     F2(X, MyArr)
 5: End Sub
 6:
 7: Private Sub F2(ByVal A As Integer, ByVal Dat() As Integer)
 8:     MsgBox(A)
 9:     MsgBox(Dat(0))
10: End Sub

                

このメソッドを呼び出しているのは 4 行目です。配列の場合は、配列の変数名だけ 「MyArr」 を渡します。

まず、1 つ注意すべき点は、7 行目で引数として 「Dat()」 と宣言しているのは、引数が配列変数であることを意味しますが、引数となるのは配列の実体ではなく、配列の実体を参照する参照型の配列変数であるという点です。

それから、それぞれの引数宣言において、ByVal がついていることから、引数自体は値渡しであるという点に注意する必要があります。つまり、7 行目のメソッドの A、Dat という仮引数のメモリ領域と呼び出し側である 4 行目の X、MyArr のメモリ領域は別であり、メソッド呼び出しの際には、前記の実引数である X、MyArr 自体の値が、メソッド側の仮引数である A、Dat に 「コピー」 されます。

ただし、参照型引数でコピーされるのは、あくまでデータの実体でない点に注意してください。値型の実引数 X では、1000 という値がメソッド側の仮引数 A にコピーされます。しかし、参照型の MyArr では、データ実体がメソッド側の仮引数にコピーされる訳ではなく、「参照情報」 がコピーされます。つまり、配列 MyArr の各要素はコピーされません。呼び出し元の MyArr と、呼び出された先の Dat とは同じ配列実体を参照しています。

これらの関係を図示すると、以下のようになります(図 3-18)。

図 3-18 値型と参照型の引数

図 3-18 値型と参照型の引数


これが示すとおり、参照型の引数では、引数として渡るのはあくまで参照情報であり、配列がどんなに多くの要素をもっていたとしても、データ実体の大きさが引数を渡す際のオーバーヘッドになることはありません。配列の要素が 3 個でも 5000 個でも、引数として渡すのは参照情報 1 つだけなので、データ実体の大きさによるオーバーヘッドはありません。

配列では同じ要素を参照できるので、前記の図では 「MyArr(0) = 10」 という代入と 「Dat(0) = 10」 という代入は、全く同じ配列の要素に作用します。

ただし、引数が値型にせよ参照型にせよ、実引数と仮引数は異なるメモリ領域のため、上記のメソッド F2 内で A、Dat の値を変更しても、呼び出し元の実引数 X、MyArr 自体の値は変えられません。

つまり、以下のような書き換え(10 行目と 12 行目)は、呼び出し元の変数には作用しません。

[例] メソッド F2 の中から引数の値を変えてみる

                  
 7: Private Sub F2(ByVal A As Integer, ByVal Dat() As Integer)
 8:     MsgBox(A)
 9:     MsgBox(Dat(0))
10:     A = 500 '呼び出し元の X は変化しない
11:     Dat(0) = 100  '同じ実体を参照するので MyArr(0) も変化する
12:     Dat = Nothing 'MyArr 自体の参照情報は変化なし
13: End Sub

                

では、前記の 10 行目、12 行目の記述によって、呼び出し元の変数である X や MyArr を変化させたいならどうすればよいでしょうか?

それには前項で触れたように、F2 メソッドの定義で ByVal 修飾子ではなく、ByRef 修飾子を使えばよいのです。

これで、値渡し(ByVal)や参照渡し(ByRef)と、値型や参照型とは、直接的な関連性がない別の概念であることが、より明確になったと思います。値渡しか参照渡しかの違いによって、実引数と仮引数が一身同体であるかどうかが決まり、引数のデータ型が値型か参照型かによって、引数そのものがデータ実体かそれとも参照情報であるかが決まります。

ワンポイント  

● for VB6

VB6 で配列を引数にするときは、「ByRef Dat() As Integer」 のように、ByRef 修飾子しか利用できず、配列の引数には ByVal が使えませんでした。VB .NET では、前述のように、配列引数に対して ByVal も ByRef もつけることができます。意味的には、VB6 の 「ByRef Dat() As Integer」 は、VB .NET の 「ByVal Dat() As Integer」 に近いでしょう。


<< 前のページ  次のページ>>
目次
Page view tracker