Visual Basic 初級講座 |
Visual Basic 中学校 > 初級講座 >
第11回 メソッドを作る
今回はメソッドを作る方法とその利用の仕方を説明します。今回説明するのはメソッドの作り方の基本部分ですが、それだけでも多くのメソッドを作れるようになります。
この回の要約 ・メソッドはプログラムを整理するために作る ・種類別のメソッドの作り方 ・メソッドを利用する例 |
この初級講座では、今までほとんどのプログラムをイベントプロシージャに書いてきました。しかし、実際にはイベントプロシージャしか使わないプログラムはごく限られた機能を持つものだけです。
VBではイベントプロシージャ以外のプロシージャも自由に作ることができます。このようなプロシージャをメソッドまたは関数とよんだりします。メソッドや関数というとVBにあらかじめ用意されているものを使うという印象しかない方もいらっしゃるかもしれませんが、このようにVBでは自分でメソッドを作って自分で呼び出すこともできるのです。
これから説明しますが、今回は下のサンプルのようなプログラムの仕組みと作り方を説明します。
Private Sub
Button1_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
Button1.Click WriteLog("C:\Test.txt", "自作メソッドのテスト") End Sub |
Private
Sub WriteLog(ByVal
FileName As String,
ByVal Description As
String)
Dim Writer As New IO.StreamWriter(FileName, True) Writer.WriteLine(Now.ToString("yyyy/MM/dd hh:mm:ss") & " " & Description) Writer.Close() End Sub |
■リスト1:ログファイルに書き込むメソッド
このプログラムではWriterLogというメソッドを自分でプログラムしています。このメソッドはファイル名と文字列を渡すと、そのファイルに日付と時間とともに文字列を書き込みます。Button1のClickイベントからはこのメソッドを呼び出しています。
メソッドの作り方を説明する前に、メソッドを作る利点を説明します。
なお、 一口に「メソッドを作る」といっても、普通の画面を持ったプログラム内にメソッドを作る場合と、クラスを自作している場合にメソッドを作る場合とでは作り方は同じでも意味合いが違います。ここでは、普通の画面を持ったプログラム内にメソッドを作る場合を説明します。
メソッドの利点はプログラムを整理できることです。整理されたプログラムは、機能追加や修正が簡単です。また、他の人がプログラムを見た場合にもプログラムを容易に理解することができます。
では、どのように整理できるのかもう少し細かく見てみましょう。
・同じような処理を一箇所にまとめられる。
プログラムのいたるところに似たような処理を書くのではなく、その処理をメソッドとして独立させ、各所からはそのメソッドを呼び出すようにするとプログラムはぐっと整理されます。
・面倒な処理を一箇所にまとめられる。
複雑な手順が必要な処理はメソッドとして独立させると、あとはそのメソッドを呼び出せばいいだけなのでとても楽になります。
・意味のある処理を一箇所にまとめられる。
プログラム中で一回しか書かないコードでも独立させてメソッドにしたほうが分かりやすい場合もあります。
たとえば、OKボタンをクリックしたらファイルにデータを書き込むプログラムを考えた場合、ファイルにデータを書き込む部分をOKボタンのClickイベントに書かないで、メソッドとして独立させます。
プログラマはOKボタンのことを気にすることなく、データ書き込み処理をプログラムすることができますし、OKボタンをクリックしたときに別の処理も行おうとした場合、やはりデータ書き込み処理を気にすることなくプログラムを変更できます。
それでは、いよいよメソッドの作り方を説明します。メソッドを作るのは覚えてしまえばいたって簡単です。
細かい説明をする前に作り方の要点を表にまとめておきます。
引数がなく、値を返さないメソッド | Private Sub methodname() End Sub |
引数があり、値を返さないメソッド | Private Sub methodname(arg1 As type1) End Sub |
引数がなく、値を返すメソッド | Private Function methodname() As type1 Return value1 End Function |
引数があり、値を返すメソッド | Private Function methodname(arg1 As type1) As type2 Return value1 End Function |
■表1:まとめ
もっともシンプルなのは、引数がなく値を返さないメソッドです。
このようなメソッドは単純に処理の一部を独立させるために使います。処理の一部を独立させることにより、同じ処理をあちこちに書く必要がなくなりますし、プログラムがわかりやすくなります。
引数が必要ない場合、このメソッドの宣言の構文は次の通りです。(引数がある場合は少し後で説明します。)
Private Sub methodname() End Sub |
■構文1:値を返さないメソッド
methodnameの部分には、メソッドの名前を設定してください。そして、メソッドの内容はイベントプロシージャ同様、Private Sub 〜 End Sub の間に記述してください。
Subステートメント(読み方:Sub = サブ)は値を返さないメソッドを作ります。その前に書かれているPrivate(読み方:Private = プライベート)はこのメソッドがどのようにアクセスできるかを指定しているのですが、今はあまり気にしないでおきます。
これを使って現在時刻を表示するメソッドShowNowを作るとすると次のようになります。ShowNowの部分は自分で好きな名前をつけて構いません。
Private
Sub ShowNow() MsgBox(Now) End Sub |
■リスト2:現在の時刻を表示するメソッド
この例ではメソッドの内容は1行しかありませんので実用的ではありません。あくまで説明用と思ってください。というのもこのタイプのメソッドは複雑なプログラムでこそありがたみがでてくるものなのです。そして今複雑なプログラムを紹介すると本題がわかりにくくなっていしまいます。
このメソッドを呼び出すには呼び出したい位置に次のように記述するだけです。
ShowNow
また、Callステートメントを使って次のように記述することもできます。
Call ShowNow
なお、Now(読み方:Now = ナウ)は現在の日付と時間を表すプロパティです。DateAndTimeクラス(読み方:DateAndTime = デイトアンドタイム)のプロパティなのですが、このクラスはStrings等と同様VBにはじめから組み込まれている省略可能な特別なクラスなのでDateAndTime.Nowではなく、ただ単にNowと書くのが一般的です。
同じように引数のあるメソッドを作る場合は、かっこの中に引数の名前と型を順番に指定します。
はじめにでてきた、テキストファイルに日付と時刻をつけて文字を書き込むWriteLogメソッドは次のようになります。 このメソッドはログファイルを作成するときにそのまま使えます。
Private Sub
WriteLog(ByVal
FileName As String,
ByVal Description As
String) Dim Writer As New IO.StreamWriter(FileName, True) Writer.WriteLine(Now.ToString("yyyy/MM/dd hh:mm:ss") & " " & Description) Writer.Close() End Sub |
■リスト3:ログをファイルに書き込むメソッド
このメソッドはFileNameとDescriptionの2つの引数を取ります。 名前から想像が付くと思いますが、FileNameで指定したファイルにDescriptionで指定した文字列を書き込みます。このように引数や変数の名前はその引数や変数の機能が想像できるようなものにしてください。
引数の宣言の前にあるキーワードByVal(読み方:ByVal = バイバル)はこのメソッドの中でこの引数の値を変更しても呼び出し元には影響しないことを表していますが、特に気にする必要はありません。省略してもVBが自動的につけてくれるのでキーボードから打ち込む必要すらありません。
引数として宣言したFileNameとDescriptionはこのプロシージャ(つまりWriteLogメソッド)の中では通常の変数と同じように扱うことができます。この2つの引数に実際にどのような値が入ってくるかは呼び出し側が決めることです。
このメソッドを呼び出すには呼び出したい位置に次のように記述します。
WriteLog("C:\Test.txt", "メソッドのテスト")
先頭にCallステートメントをつけて次のように書くこともできます。
Call WriteLog("C:\Test.txt", "メソッドのテスト")
そのほか、プログラム中にファイルへの書き込みに関するコードがありますが、これらについては今回は説明しません。
値を返すメソッドは別名「関数」とも呼ばれます。このようなメソッドは処理の一部を独立させたり、複雑な処理の補助に使います。プログラムが巨大で複雑になるほど関数の必要性は増します。一定以上の規模のプログラムでは関数を作らないなどということは考えられません。
引数が必要ない場合、このメソッドの宣言の構文は次の通りです。(引数がある場合は少し後で説明します。)
Private Function methodname() As typename
End Sub |
■構文2:値を返すメソッド。別名「関数」。
値を返すメソッドを宣言するにはSubではなく、Functionステートメント(読み方:Function = ファンクション)を使用します。
返す値の型はメソッドの宣言とともに指定する必要があります。上記の雛形(ひながた)ではAs typenameと書いておきましたが、このtypenameのところに返す値の型を指定します。
またメソッド内で返す値を指示するにはReturnステートメント(読み方:Return = リターン)を使用します。 上記の雛形でvalueのところに返したい値を指定してください。なお、Returnが実行されるとその下にあるコードは一切実行されないので注意してください。
たとえば、次のメソッドは日本の元号で年を返します。たとえば、2005年にこのメソッドを呼び出すと、17を返します。2005年は平成17年だからです。
Private
Function
GetJapaneseYear() As Integer Dim Japan As New Globalization.JapaneseCalendar Dim JapaneseYear As Integer JapaneseYear = Japan.GetYear(Now) Return JapaneseYear End Function |
■リスト4:現在の年を和暦で返す。
この例も内部のコードの説明は割愛します。
このメソッドは通常のメソッドと同じように呼び出したり、値を受け取ったりできます。
なお、あまり使いませんが、Returnステートメントを使わなくても「メソッド名 = 戻り値」という記述で値を返すこともできます。
上の例で言うと Return JapaneseYearではなく、GetJapaneseYear = JapaneseYear という記述も可能ということです。
この2つは書き方が違うだけでなく機能も少し違います。Returnが実行された場合、その行より下にプログラムがあってもすべて無視されますが、「メソッド名 = 戻り値」という書き方の場合、その行より下にあるプログラムも実行されます。
最後に値を返すメソッドで引数が必要な場合について説明します。といってもここまでの説明ですでに十分だと思いますので、このパターンは例だけ示しておきます。次のメソッドIsEvenは引数が偶数の場合True、そうでない場合Falseを返します。
Private
Function IsEven(ByVal
Value As
Integer)
As
Boolean Return Value Mod 2 = 0 End Function |
■リスト5:数値が偶数であるか判断する
これも内部のコードの説明は省略します。 = 演算子については後日説明する予定です。要は「2で割ったあまりが0だったら偶数」という算数の基本的なことを行っているに過ぎません。
このメソッドは通常のメソッドと同じように呼び出したり、値を受け取ったりできます。
以上、基本的なメソッドの作り方を説明しましたが、メソッドにはもっとこったことができます。たとえば、引数の数を可変にすることができます。また、引数を省略可能にすることもできます。それから名前は同じだけれども引数が異なるメソッドを作ることもできます。
しかし、これらの高度なメソッドの作り方は以下で簡単に紹介するにとどめ、詳細はまたの機会に説明します。
メソッド自体に組み込めるさらなる高度な機能
・可変引数
キーワードParamArrayを使用すると、引数の数を固定ではなく可変にできる。
・省略可能引数
キーワードOptionalを使用すると、省略可能な引数とその既定値を設定できる。
・多重定義
キーワードOverloadsを使用すると、同じ名前で異なる引数のメソッドを複数作れる。
・上書き
キーワードOverridesまたはShadowsを使用すると、あらかじめ用意されているメソッドの内容を変更できる。
・引数の値を変更する
キーワードByRefを使用すると、メソッドの中で引数の値を変更して呼び出し元に反映することができる。
最後に、メソッドを作る利点を感じ取っていただくためにちょっと複雑なプログラムを用意してみました。
このプログラムはキーボードの画面上のボタンをクリックすることにより円を左右に移動させるプログラムです。
円はグラデーションで描画します。背景色は黒です。
■画像1
まずフォームに次の表のように2つのボタンを配置してください。
コントロール | プロパティ | 値 |
btnLeft (Button) | Text | < |
btnRight (Button) | Text | > |
■表2
さて、イベントプロシージャだけでこのプログラムを書くと次のようになります。
Dim
CircleX As Integer = 150 Dim CircleY As Integer = 100 |
Private Sub btnLeft_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLeft.Click CircleX -= 5 Dim g As Graphics = Me.CreateGraphics Dim b As New Drawing2D.LinearGradientBrush(New Point(CircleX, CircleY), New Point(CircleX + 50, CircleY + 50), Color.Yellow, Color.LightBlue) g.Clear(Color.Black) End Sub |
Private
Sub
btnRight_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
btnRight.Click
CircleX += 5 Dim g As Graphics = Me.CreateGraphics Dim b As New Drawing2D.LinearGradientBrush(New Point(CircleX, CircleY), New Point(CircleX + 50, CircleY + 50), Color.Yellow, Color.LightBlue) g.Clear(Color.Black) End Sub |
■リスト6:メソッド化されていない煩雑なコード
これでも問題なく動くのですが、無駄がいっぱいです。薄いピンク色で塗ってある部分はまったく同じ処理なので2箇所に分断されているのはよくありません。ここでメソッドの出番です。メソッドを使って円を描画する部分を一箇所にまとめるてみましょう。次のようになります。新しいメソッドにはDrawCircleという名前をつけました。
Dim
CircleX As Integer = 150 Dim CircleY As Integer = 100 |
Private Sub btnLeft_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLeft.Click CircleX -= 5 DrawCircle(CircleX, CircleY) End Sub |
Private
Sub
btnRight_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
btnRight.Click
CircleX += 5 DrawCircle(CircleX, CircleY) End Sub |
Private
Sub DrawCircle(ByVal
X As
Integer,
ByVal Y
As
Integer)
Dim g As Graphics = Me.CreateGraphics Dim b As New Drawing2D.LinearGradientBrush(New Point(X, Y), New Point(X + 50, Y + 50), Color.Yellow, Color.LightBlue) g.Clear(Color.Black) End Sub |
■リスト7:メソッド化されて整理されているコード
これですっきりしました。機能は修正前と全く一緒ですが、円を描く部分が一箇所にまとまっているので色や大きさを変えるのが簡単ですね。円の色や大きさを変える手間が修正前のプログラムの半分になっている点に注意してください。これだけの規模のプログラムでも手間が軽減できるのですから、ある程度まとまったプログラムで以下にメソッド化(関数化)が物を言うかわかります。
さらに、左右だけでなく上下にも移動させたいと思ってもとても簡単にできてしまいますね。
ところで、DrawCircleメソッドは座標XとYを引数に取るように作りましたが、構文上は引数としてXとYが必要なわけではありません。XとYは引数にしなくても共通変数のCircleXとCircleYを参照すればよいのですから問題ないはずです。
しかし、この場合座標はあくまで引数にするのが良いです。ここのところあまり市販の本や雑誌には書いていませんが、まともなプログラマならみんなわかっていることなのでよく聞いてください。以前にも説明したように、メソッドとは正しい引数を渡せば予想通りの動作をしてくれるものなのです。メソッドを呼び出す側ではメソッドの中身がどうなっているかは気にしなくて良いのです。
■画像2:良いメソッド
しかし、ここでDrawCircleメソッドが共通とはいえメソッドにとっては外部の変数CircleXとCircleYを参照するとなると、メソッドの呼び出し側では、メソッドの呼び出し方の他に、共通変数の値も管理しないとメソッドが正しい動作をしてくれないということになってしまいます。これではメソッドの管理が煩雑になり、プログラムが複雑になっていくにつれ思わぬバグの原因となってしまうのです。
■画像3:良くないメソッド
ここで混乱を招いてしまうかもしれませんがもう1つ補足しておきます。メソッドの中には多機能を前提に設計されているメソッドもあります。 これは初めの方に紹介した「面倒な処理を一箇所にまとめられる。」という利点を持つメソッドのことです。そのようなメソッドの場合は外部の変数などに依存しても問題ありません。
どのメソッドが多機能なメソッドでどのメソッドが通常のメソッドなのかの切り分けの大部分は経験によるしかないと思いますが、私の経験では自作するメソッドの9割は通常のメソッド、つまり外部の変数に依存しないメソッドとなります。