Visual Basic 初級講座
VB.NET 2002 対応 VB.NET 2003 対応 VB2005 対応 VB2008対応

 

Visual Basic 中学校 > 初級講座 >

第46回 クラスの作成

クラスはVBでプログラムを行ううえで決定的な効果を発揮します。今回からの数回でこのクラスの作り方を説明します。

概要

・クラスは意味のある一塊(ひとかたまり)の機能があつまったもの。

・クラスはできるだけ現実世界の「物」をモデルに設計する。

・単純にクラスを作成するには次の通り記述するだけ。

Public Class MyClassName

End Class

・インスタンスが作成されたときにクラスの初期処理を実行するにはコンストラクタを利用する。コンストラクタは名前がNewであるメソッドのこと。

Public Sub New()

'ここに初期処理を書く

End Sub

・ クラスが破棄されたときにクラスの終了処理を実行するにはデストラクタとしてDisposeメソッドを記述する。(ただし、これを有効に活用するために本文には紹介する形式を利用しなければならない)

Implements IDisposable

Public Sub Dispose() Implements IDisposable.Dispose

    'ここに終了処理を書く

End Sub

 

1.方針

 

今回からは数回にわたってクラスの作り方を説明します。初級講座では既にクラスを利用する方法について説明してきました。今度はクラスを作る側の立場に立ちます。

VBのプログラムはクラスを基盤として成り立っており、クラスの作り方も知らないで初級講座を卒業することは到底考えられません。とは言え、クラスに関する事柄は奥が深いので初級講座ですべてを説明するのも現実的ではありません。

そこで、ここではクラスの作り方を形式的な側面から説明することにします。その意味や思想についての大部分は中級講座以降で説明します。

形式的な側面といっても、それさえ抑えておけばとりあえずクラスを作成することができるようになるわけで、いろいろとクラスを作って経験を積んでいるうちに見えてくることもあるでしょう。

今回はシンプルなクラスの作成例を紹介し、クラスが作成された場合に自動的に実行されるコンストラクタという初期処理と、クラスが破棄された場合に実行されるデストラクタという終了処理について説明します。

次回からはクラスにメソッド・プロパティ・イベントを組み込む方法をじっくりと説明し、一通り読めばクラス作成について必要な形式的な知識が整うように配慮するつもりです。

みなさんにお願いしたいのは私が取り上げる役に立たないサンプルについて笑って許して欲しいということです。実際にクラスを作成する場合はプログラムの構造上必要があって作成する場合がほとんどなのですが、ここでそのようなプログラムの構造の話からしていてはなかなか本題であるクラスの話に入れません。

ですから、私はたいていの場合このような背景を省略していきなりクラスの例を紹介することになります。しかし、このような事情で私のサンプルはあまり役に立たないつまらないクラスの例となることがほとんどでしょう。

さて、前置きを終えたところでそろそろ本題である深遠なるクラスの話を始めます。

発展学習  -  継承・多態性(ポリモーフィズムス)・隠蔽(カプセル化)

発展学習では意欲的な方のために現段階では特に理解する必要はない項目を解説します。

少しクラスについてかじったことのある方なら、継承・多態性(ポリモーフィズムス)・隠蔽(カプセル化)と言ったような言葉を聞いたことがあるでしょう。抽象クラス・インターフェースという言葉も聞いたことがあるかもしれません。

クラスについての詳しい説明を求めている方のために、初級講座でのクラスの説明ではこれらの事柄はほとんど扱わないことをあらかじめ宣言しておきます。

こういった事柄は中級講座以降で説明する予定です。

 

 

2.クラスが目指すもの

 

クラスとは意味のある機能のあつまりです。プログラムをしているとファイルシステムやデータベース、ネットワーク、グラフィックスやサウンドなどと実にいろいろな機能を使用することになります。それらの機能を意味のある単位ごとにわけて管理しているのがクラス(や構造体)です。

そして、クラス(や構造体)はただ単に機能をまとめているだけではなく、人間にとって扱いやすいように現実世界に存在する「物」をモデルとして設計されているのが特徴です。

たとえば、ファイルを操作するFileInfoクラスは現実の「ファイル(書類)」のように設計されています。このことはメソッドやプロパティの意味を考えてみると明らかです。

メソッド・プロパティ 英語の意味 プログラム上の機能
Open 開け 開く
AppendText 文字を追加せよ 文字を追加する
Length 長さ ファイルサイズ
Name 名前 名前

■表1

現実世界には存在しないような物をクラス化する場合でもできるだけ人間の視点でわかりやすく、できるだけ「物」のように設計されています。

そして、クラスを利用する側の立場に立てば、必要な物を実体化し、その物の性質を定義したり、物に対して命令したりして目的の機能を実現していくのがVBのプログラミングスタイルです。

このスタイルは人間にとってとてもわかりやすいプログラミングスタイルであると同時に、意味ごとに機能が分かれていて管理・設計がしやすいなどの利点がありVB以外にも多くのプログラミング言語で採用されています。

「物(オブジェクト)」視点でのプログラミングということで、このスタイルは「オブジェクト指向」と呼ばれています。

 

3.はじめてのクラス作成

 

さて、クラスを作成する場合にはまずそのクラスがどのような機能のまとまりであるか現実世界の「物」の即して考えてみてください。これはとても重要なことです。

オブジェクト指向の枠からはみ出したクラスを作ってしまうととても扱いにくいクラスができあがってしまうことが多いです。クラスは単体ではなくほかのクラスと連携して動作する場合がほとんどであることもその理由です。

発展学習  -  クラスとオブジェクト指向

発展学習では意欲的な方のために現段階では特に理解する必要はない項目を解説します。

VBはオブジェクト指向に基づいてプログラムしたときに最高の機能・性能が発揮できるように設計されています。クラスはこのオブジェクト指向プログラミングを行ううえでもっとも重要な要素です。そのためクラスを設計するにはただ単に似たような機能を集めた便利な入れ物ということ以上に、オブジェクト指向にのっとった設計が必要となるのです。

よく言われるオブジェクト指向の3大機能として継承、多態性、隠蔽があります。これらの詳細は中級講座で扱いますが、こういったこともしっかり把握していないと良いクラス設計は行えません。現実世界の「物」に即したクラスを設計することはオブジェクト指向プログラミングの第一歩です。

とは言え、話を簡単にするためにまずは特に意味のないメソッドを1つ持つだけの単純なクラスを作ってみましょう。

まずはいつも通り新しいプロジェクトを作成してください。Windowsアプリケーションで、名前は何でもよいのですが私は「ClassTest」としました。

プロジェクトを作成したら[プロジェクト]メニューで[クラスの追加]を選択してください。

そうすると追加可能なファイルの例が一覧表示されます。この一覧はあらかじめ用意されているテンプレートと呼ばれる例が表示されているだけで、自分でプログラムすればこの一覧にないタイプのクラスを実現することも可能です。ある程度決まりきったプログラムをマイクロソフトが親切心でパターン化して用意しておいてくれているだけです。

今回はシンプルに「クラス」を選択して、名前には「Telephone」と入力して「追加」ボタンを押してください。

Telephoneはテレフォン、つまり電話のことです。

クラスの追加

■画像1:クラスの追加

そうするとソリューションエクスプローラにTelephone.vbが追加され、空のクラスのコードも作成されます。

ソリューションエクスプローラ上のクラス

■画像2:ソリューションエクスプローラ上のクラス

空のコードは次のようになっています。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

Public Class Telephone

End Class

■リスト1:名前だけ宣言した空のクラス

このコードを見ればすぐわかるようにクラスを作るときは必ずClass End Class(読み方:Class = クラス)という記述をします。

クラスの内容はこの間に書いていくことになります。

Public(読み方:Public = パブリック)というのはこのクラスがどこからでも呼び出して利用できることを示しています。Publicで宣言したクラスは別のプロジェクトやアプリケーションからも呼び出せます。現在のプロジェクトでのみ使用できるように制限したい場合はFriend(読み方:Friend = フレンド)を使って、Friend Class Telephoneのように宣言します。別のプロジェクトとの連携については第51回 クラスライブラリで説明します。

では、このクラスに電話を鳴らすBellメソッド(読み方:Bell = ベル)を記述してみましょう。以下の通りにしてください。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

Public Class Telephone

    Private
Declare Ansi Function PlaySound Lib "winmm.dll" Alias "PlaySoundA" (ByVal lpszName As String, ByVal hModule As Integer, ByVal dwFlags As Integer) As
Integer

   
Private Const SND_ASYNC = &H1

    Public
Sub Bell()

        Dim
WaveFile As
String

       
WaveFile = Environment.GetEnvironmentVariable("WINDIR") & "\media\ringin.wav"
       
PlaySound(WaveFile, 0, SND_ASYNC)

    End
Sub

End
Class

■リスト2:Telephoneクラス内にBellメソッドを定義したところ

音を鳴らす機能をプログラムしたのでちょっと複雑になってしまいましたがこんなものです。メソッドの本体はSubEnd SubまたはFunctionEnd Functionの間に記述します。今回は戻り値がないのでSubを利用しました。

1つのポイントとしてメソッドの宣言がPrivate SubではなくPublic Subとなっている点に注目してください。Privateで宣言したメソッドはそのクラスの中からしか呼び出せなくなります。Publicで宣言したメソッドはクラスの外側から呼び出せるようになります。今回はクラスの外側にあるフォームからこのメソッドを呼び出そうとしているのですからPublicで宣言しなければなりません。

この他、Friendで宣言すると現在のプロジェクト内でのみ呼び出せるメソッドを作成できます。

  読み方 適用範囲
Private プライベート 宣言されているクラスの内部のみ
Friend フレンド 宣言されているプロジェクトの内部のみ
Public パブリック どこからでも呼び出せる。別プロジェクトからでも呼び出せる。

■表2:適用範囲を示すキーワード

適用範囲については図解基礎解説 宣言の効果でまとめているので必要に応じて参照してください。

メソッドの作り方については初級講座第11回 メソッドを作るを参照してください。またクラス作成の視点でのメソッドの作成方法については次回説明予定です。

あとはこのBellメソッドを使って電話を鳴らすプログラムを書きます。フォームにボタンを貼り付けてClickイベントに次の通り記述してください。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim
Tel As New Telephone

    Tel.Bell()

End
Sub

■リスト3:フォーム側のプログラム。TelephoneクラスのBellメソッドの呼び出し。

これで完了です。実行してボタンを押すとスピーカーから電話のベルの音が聞こえてきます。音源がないパソコンではベルが鳴りません。

注目していただきたいのはClickイベント内のプログラムです。たった2行しかありませんが通常のクラスを使ったプログラムとまったく同じようになることがわかっていただけると思います。

そして、このクラスが簡単に利用できることも考えてみてください。電話を鳴らすにはBell(=鳴れ)と命令するだけです。これこそがクラスの理想的な利用方法です。実現したい機能を呼び出すだけです。

逆にクラスを作る側では呼び出し側でこのようにシンプルな利用ができるようにクラスを設計しなければなりません。たとえば、このBellメソッドを次のように呼び出さなければいけないとすると悪い設計ということになります。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim
Tel As New Telephone

   
'これは悪い例です。
    'この例は実際には動作しません。
   
Tel.WaveFileName = "ringin.wav"
   
Tel.Bell()

End
Sub

■リスト4:悪い例。フォーム側のプログラムをこのように書かなければならないとしたらひどいクラス。

この設計が悪い理由は、電話を鳴らすために2つの呼び出しを行わなければならない点です。また、電話の呼び出し音として使用するwavファイルの指定はプログラムの技術的なことであって「電話」という物とは無関係なことです。利用する側は本質的なことだけを命令すれば良いようにすべきで、技術的なことと「物」の本質的なことの両方を制御しなければならないとしたら折角のクラスも使いにくく、便利で簡単とは言えなくなってしまうでしょう。

メモ  -  VB2005ではもっと簡単に音が鳴らせます

Bellメソッドは電話のベルの音としてringin.wavを鳴らします。このときVB.NET2002およびVB.NET2003にはwavファイルを再生する機能がないので、Windowsのマルチメディア機能を呼び出すコードを書かなければなりません。それがPrivate Declare Ansi Function PlaySound...の部分です。このような記述でWindowsの機能を呼び出す方法については中級講座で説明します。

しかし、VB2005ではMy.Computer.Audio.Playメソッドを呼び出すだけですぐにwavファイルが再生できるのでTelephoneクラスの内容をもっと簡単に記述することが可能です。

 

 

4.コンストラクタ

 

コンストラクタとはクラスのインスタンスが作成されたときに一番最初に実行されるコードのことです。たとえばよくあるようにNewを使ってクラスのインスタンスを作成した場合、その瞬間に実行されるコードをプログラムすることができます。それがコンストラクタです。

具体的なコンストラクタの話を説明するために 今度はWebからファイルをダウンロードしてくるクラスを作ります。そのようなクラスとしては既にWebClientクラス(読み方:WebClient = ウェブクライアント)が存在しますが、我々が作るクラスはこのWebClientクラスのダウンロード機能に加えて、ダウンロードしたものの履歴を 自動的に作成する機能を組み込むことにします。

まずは、新しくクラスを作成してください。名前はずばり「DownloadCommander」、つまりダウンロード司令官としましょう。

さしあたってクラスにはダウンロードするためのDownloadメソッドが必要ですから内容を次の通りにしてください。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

Public Class DownloadCommander

    Dim wc As Net.WebClient = New Net.WebClient()

    Public
Sub Download(ByVal URL As String, ByVal FolderName As String)

        Dim
FileName As
String

       
FileName = FolderName & "\" & IO.Path.GetFileName(URL)
        wc.DownloadFile(URL, FileName)

    End
Sub

End
Class

■リスト5:DownloadCommanderクラスとそのDownloadメソッド

これだけでダウンロード機能は完成です。このクラスを利用するフォーム側では次のようにプログラムするとあなたのパソコンのCドライブに初代総理大臣である伊藤博文の写真がコピーされます。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim
Commander As New DownloadCommander
    Dim URL As String =
"http://www.kantei.go.jp/jp/rekidai/souri/images/souri01.jpg"
   
Dim Folder As String = "C:"

   
Commander.Download(URL, Folder)

End
Sub

■リスト6:フォーム側のプログラム。DownloadCommanderクラスのDownloadメソッドの呼び出し。

ここまでの処理がとても簡単な理由は最初に書いたように既にあるWebClientクラスの機能をそのまま使っているだけだからです。

次はダウンロード履歴をログファイルとして作成する部分をプログラムしてみます。このためにとりあえずDownloadメソッドにログファイル名を指定する引数を追加して次のように改造します。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

Public Class DownloadCommander

    Dim wc As Net.WebClient = New Net.WebClient()

    Public
Sub Download(ByVal URL As String, ByVal FolderName As String, ByVal LogFileName As String)

        Dim
FileName As
String

       
'▼ファイルのダウンロード
       
FileName = FolderName & "\" & IO.Path.GetFileName(URL)
        wc.DownloadFile(URL, FileName)

       
'▼履歴をログに書き込む
       
Dim Writer As New IO.StreamWriter(LogFileName, True)
        Dim Description As
String

       
Description = Now.ToString("yyyy/MM/dd hh:mm:ss") & ", "
       
Description &= Environment.UserName & ", "
       
Description &= URL & ", "
       
Description &= FileName

        Writer.WriteLine(Description)
        Writer.Close()

    End
Sub

End
Class

■リスト7:DownloadCommanderクラスのプログラム。Downloadメソッドを改造してログの記録を実現したもの。

呼び出し側も新しく追加された引数に対応しなければならないので次のようになります。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim
Commander As New DownloadCommander
    Dim URL As String =
"http://www.kantei.go.jp/jp/rekidai/souri/images/souri01.jpg"
   
Dim Folder As String = "C:"
   
Dim LogFileName As String = "C:\DLLog.txt"

   
Commander.Download(URL, Folder, LogFileName)

End
Sub

■リスト8:フォーム側のプログラム。ログ記録機能つきのDownloadメソッドの呼び出し。

これで指定したファイルをダウンロードしてログに履歴を記録してくれるメソッドを持ったクラスが完成しました。

でも、ちょっと使いにくくないですか?ダウンロードするだけなのに3つも引数を指定しなければいけないなんて好ましくありません。普通に考えてもダウンロード元のURLと保存先のパスだけ指定すれば良いだけにしたいですし、私個人としてはURLだけ指定すればあとは適当に処理してくれるような仕組みになっていればもっと楽です。

この点を改良するためにはいくつかの方法があります。ここでコンストラクタが登場します。

通常はクラスはメソッドなどが呼び出されたりしたときにそれに対応するコードが実行されるだけなのですが、クラス側にもいろいろ準備が必要ということでインスタンスが生成された時点でコードを実行するコンストラクタという仕組みが用意されているのです。

それで、今回はダウンロード先のフォルダの設定と、ログファイルの設定はこのコンストラクタにまかせて、メインであるDownloadメソッドではURLだけ指定すれば良いようにしてしまいましょう。

コンストラクタは名前がNewであるメソッドのことです。名前がNewであるメソッドを定義すると自動的にそれがコンストラクタになります。まずは軽く実験して見ましょう。クラス側に次のプロシージャを追加してください。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

Public Sub New()

    MsgBox(
"コンストラクタが呼び出されました!")

End
Sub

■リスト9:DownloadCommanderクラス内のプログラム。コンストラクタのテスト。

これで実行するとダウンロードする前に「コンストラクタが呼び出されました!」というメッセージが表示されます。ステップ実行するとこのメッセージがDim Commander As New DownloadCommanderの行で呼び出されることが確認できます。

補足  -  ステップ実行する方法

[デバッグ]メニューの[ステップイン]を選択すると、最初からステップ実行モードでプログラムを起動できます。初級講座第41回 実行の一時停止とデバッグも参照してください。

ではこのコンストラクタでダウンロードしたファイルの保存先フォルダの指定と、ログファイルの指定ができるように改造しましょう。コンストラクタはインスタンスが作成されたときに実行されるという以外では普通のメソッドと同じなのでこのように追加情報が必要なコンストラクタを作成するには引数を使えば足ります。

コンストラクタで受け取ったファイル名はクラスレベルで宣言した変数に保存しておくことにします。

また、コンストラクタに引数を追加する分Downloadメソッドからは引数を削りましょう。

これらを考え合わせるとDownloadCommanderクラスは次のようになります。XMLコメントも追加しました。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

''' <summary>ファイルをダウンロードします。</summary>
Public Class DownloadCommander

    Dim
wc As Net.WebClient = New Net.WebClient()

    Public
FolderName As
String
   
Public LogFileName As String

   
'■コンストラクタ
   
''' <param name="Folder">ダウンロードしたファイルの保存先フォルダ</param>
   
''' <param name="LogFile">ダウンロード履歴を記録するファイル</param>
   
Public Sub New(ByVal Folder As String, ByVal LogFile As String)

       
Me.FolderName = Folder
       
Me.LogFileName = LogFile

    End
Sub
    '■Download
   
''' <summary>ファイルをダウンロードします。</summary>
   
''' <param name="URL">対象のファイルのURL</param>
   
''' <remarks>ダウンロードしたファイルはFolderNameプロパティで指定したフォルダに保存されます。</remarks>
   
Public Sub Download(ByVal URL As String)

        Dim
FileName As
String

       
'▼ファイルのダウンロード
       
FileName = FolderName & "\" & IO.Path.GetFileName(URL)
        wc.DownloadFile(URL, FileName)

       
'▼履歴をログに書き込む
       
Dim Writer As New IO.StreamWriter(LogFileName, True)
        Dim Description As
String

       
Description = Now.ToString("yyyy/MM/dd hh:mm:ss") & ", "
       
Description &= Environment.UserName & ", "
       
Description &= URL & ", "
       
Description &= FileName

        Writer.WriteLine(Description)
        Writer.Close()

    End
Sub

End
Class

■リスト10:DownloadCommanderクラスのプログラム。コンストラクタの実装。

コンストラクタに引数が追加され、Downloadメソッドから引数が削除されたため呼び出し側のコードも変更が必要となります。

フォーム側のコードは次のようになります。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim
URL As String =
"http://www.kantei.go.jp/jp/rekidai/souri/images/souri01.jpg"
   
Dim Folder As String = "C:"
   
Dim LogFileName As String = "C:\DLLog.txt"
   
Dim Commander As New DownloadCommander(Folder, LogFileName)

    Commander.Download(URL)

End
Sub

■リスト11:フォーム側のプログラム。新しいコンストラクタとDownloadメソッドの呼び出し。

この改造の成果で、Downloadメソッドを呼び出すときは対象のURLを指定すればよいだけのスマートな形ができあがりました。

発展学習  -  もっとスマートに

発展学習では意欲的な方のために現段階では特に理解する必要はない項目を解説します。

しかし、これでもまだまだ使いやすい便利なクラスといえません。たとえば、Downloadメソッドでは保存先のフォルダを指定しても良いし指定しなくても良いとなればプログラム手法の選択肢が増えて便利です。また、インスタンスを作成するときに必ず2つの引数を指定しなければならないというのは実は面倒です。とりあえず先にインスタンスを作成しておいて、必要な情報は後で指定できるような方法を用意しておけばもっとよくなるでしょう。

こういったクラスの設計に関する細々したことを実現する手法は次回以降で説明する予定です。

とは言え、よく見るとDownloadメソッドは確かにシンプルに呼び出せるようになりましたが、代わりにコンストラクタに引数が追加されたので結局指定すべき情報の量は同じです。これで本当にスマートになったと言えるのでしょうか?

答えは「はい、そうです。」です。上記の例は重要なところだけピックアップしたサンプルなので違いがわかりにくいかもしれませんが、我々のDownloadCommanderクラスのメイン機能はDownloadメソッドです。Downloadメソッドを何度も呼び出す場合のことを考えてみてください。その都度3つの引数を指定しなければならないのと、最初だけ基本的な情報を設定しなければならないのとでは使い勝手が大分違います。

では、Downloadメソッドを複数回呼び出すとして、途中でダウンロード先のフォルダを変更したい場合どうすればよいでしょうか?

ダウンロード先のフォルダはコンストラクタで指定するのですからもう一度コンストラクタを呼び出すというのが1つの方法ですがこの方法は効率的ではありません。一応その都度コンストラクタを呼び出す例を紹介しておきましょう。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim
Commander As DownloadCommander

   
'▼伊藤博文
   
Commander = New DownloadCommander("C:", "C:\DLLog.txt")
    Commander.Download(
"http://www.kantei.go.jp/jp/rekidai/souri/images/souri01.jpg")

   
'▼黒田清隆
   
Commander.Download("http://www.kantei.go.jp/jp/rekidai/souri/images/souri02.jpg")

   
'▼山県有朋
   
Commander = New DownloadCommander("C:\Test", "C:\DLLog.txt")
    Commander.Download(
"http://www.kantei.go.jp/jp/rekidai/souri/images/souri03.jpg")

End Sub

■リスト12:フォーム側のプログラム。複数のDownloadメソッドを呼び出す例

この例では伊藤博文と黒田清隆(読み方:黒田清隆 = くろだきよたか)はC:に保存されますが、山県有朋(読み方:山県有朋 = やまがたありとも)はC:\Testフォルダに保存されます。

黒田清隆は2代目の内閣総理大臣で帝国憲法が発布されたときの首相でもあります。北海道開拓使官有物払下げ事件で今でも教科書に載っているでしょうか?個人的には明治天皇とは馬が合わず、また自分の妻を殺したという疑惑がいまだにささやかれていて歴史ミステリの1つとなっています。

さて、いちいちコンストラクタを呼び出すということはその都度クラスを再生成するということでとても効率が悪くなります。クラスの生成というのは効率が悪い作業の1つなのです。プログラム中ではできるだけクラスを生成する回数を減らすようにするように努力してください。

今回はDownloadCommanderクラスのFolderNameプロパティを変更するだけでダウンロード先のフォルダを変更することができます。そのようなプロパティをいつの間に作成したのか不審に思われるかもしれませんね。でも、フォームでプログラムするときにCommander.と入力すると入力候補の一覧が表示されて、その中に確かに「FolderName」という項目があるのが確認できるでしょう。

実はこれはDownloadCommanderクラスで宣言しているただの変数なのです。ただの変数なのですが宣言するときにDimではなくPublicを使って宣言しているのがポイントです。Publicで宣言した要素はクラスの外側からもアクセスすることができて、それが変数の場合はあたかもクラスのプロパティであるかのように見えるのです。

厳密にはプロパティを作成するにはPropertyキーワード(読み方:Property = プロパティ)を使用しなければならないので、このFolderNameはプロパティではなく「フィールド」と呼ばれます。しかし、このように単純に値を保存するだけの機能の場合はプロパティであってもフィールドであっても違いはありません。

→プロパティの作成方法については次回説明します。

プロパティを使って保存先を変更するプログラムは次のようになります。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim
Commander As New DownloadCommander("C:", "C:\DLLog.txt")

   
'▼伊藤博文
   
Commander.Download("http://www.kantei.go.jp/jp/rekidai/souri/images/souri01.jpg")

   
'▼黒田清隆
   
Commander.Download("http://www.kantei.go.jp/jp/rekidai/souri/images/souri02.jpg")

   
'▼山県有朋
   
Commander.FolderName = "C:\Test"
   
Commander.Download("http://www.kantei.go.jp/jp/rekidai/souri/images/souri03.jpg")

End Sub

■リスト13:フォーム側のプログラム。複数のDownloadメソッドを呼び出しとFolderNameプロパティの使用例。

これを見ると、他のマイクロソフト製のクラスを使う場合と同じようなプログラムになっている感じがしますよね。

 

5.デストラクタ

 

以上でコンストラクタを使えばクラスのインスタンスが作成されたときに実行されるコードが記述できることがわかりました。今度は終了するときに実行するコードについて説明しましょう。

終了するときに実行するコードのことをコンストラクタに対応してデストラクタと呼びます。コンストラクタはNewメソッドだけなのに対し、VBではデストラクタは2種類用意されています。

まず、コンストラクタのように気軽に使用できる終了処理としてFinalizeメソッド(読み方:Finalize = ファイナライザ)が用意されています。

先ほどのDownloadCommanderクラスに次のプロシージャを追加して実行するとFinalizeデストラクタが動作している様子がわかります。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

Protected Overrides Sub Finalize()

    MyBase.Finalize()
    MsgBox(
"デストラクタが呼び出されました。")

End
Sub

■リスト14:DownloadCommanderクラス内のプログラム。デストラクタのテスト。

このメソッドの宣言がPublic SubではなくProtected Overrides Subとなっている点は注目に値します。このことと1行目にあるMyBase.Finalize()の意味は中級講座の中で明らかになっていくことでしょう。今はこの部分は追及しないでください。

Finalizeメソッドはこのように気軽に利用できるデストラクタなのですが残念ながらできるだけ使用しないようにしてください。

なぜならばFinalizeメソッドには次の2つのデメリットが存在するからです。

Finalizeメソッドのデメリット
VBが自動的に呼び出すので、いつ呼び出されるかプログラマは制御できない。
メモリ管理とパフォーマンスでわずかな不利がある。

 

そこで、通常の終了処理にはもう1つのデストラクタであるDisposeメソッド(読み方:Dispose = ディスポーズ)を使用することになるのですが、このDisposeメソッドは気軽さの点ではFinalizeメソッドより劣ります。しかし、なれてしまえばそう難しいことではありませんからちょっと腰をすえてこれからの説明を読んでみてください。

クラスの中にDisposeメソッドを記述するには、IDisposableインターフェース(読み方:IDisposable = アイディスポーザブル)を実装しなければなりません。…と書いても何のことやらわからないと思います。インターフェースについては中級講座で詳しく取り上げる予定ですが、今便宜のために簡単に説明するとインターフェースとは.NET Frameworkにとっての目印のような働きをします。

説明するよりやってもらった方が早いと思いますので、まずDownloadCommanderクラスにインターフェースを実装する宣言を1つ追加してください。このコードはクラスの宣言のすぐ下に書く必要があります。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

Public Class DownloadCommander

    Implements IDisposable

    Dim
wc As Net.WebClient = New Net.WebClient()

   
(以下略)

■リスト15:DownloadCommanderクラス内のプログラム。IDisposableインターフェースの実装。

VB2005の場合はImplements IDisposableと記述してその行を離れた瞬間に自動的に次のコードが生成されます。プログラムをスクロールして下のほうにこの記述が追加されていることを確認してください。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

    Private disposedValue As Boolean = False ' 重複する呼び出しを検出するには

   
' IDisposable
   
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue
Then
       
    If disposing Then
           
    ' TODO: 明示的に呼び出されたときにアンマネージ リソースを解放します
           
End If

           
' TODO: 共有のアンマネージ リソースを解放します
       
End If
   
    Me.disposedValue = True
   
End Sub

#Region
" IDisposable Support "
   
' このコードは、破棄可能なパターンを正しく実装できるように Visual Basic によって追加されました。
   
Public Sub Dispose() Implements IDisposable.Dispose
       
' このコードを変更しないでください。クリーンアップ コードを上の Dispose(ByVal disposing As Boolean) に記述します。
       
Dispose(True)
        GC.SuppressFinalize(
Me)
    End
Sub
#End Region

■リスト16:DownloadCommanderクラス内のプログラム。Disposeメソッドの実装。

これはちょっと面倒なDisposeメソッドの宣言をマイクロソフトが気を利かせて自動的に挿入してくれたものです。VB.NET2002およびVB.NET2003の場合はこれらの記述は自動的には挿入されないのでここからコピーして自分で貼り付けると良いでしょう。

このコードを良く見ると宣言の違う2つのDisposeメソッドがあることがわかります。1つはProtected Overridable Sub Dispose(ByVal disposing As Boolean)と宣言されており、もう1つはPublic Sub Dispose()と宣言されています。

なにやら良くわからないかもしれませんが、マイクロソフトも配慮してくれたのでしょう、コメントがいくつかついているので参考になります。このような自動生成されたコードについてのコメントの場合、プログラマはTODOと示された位置に自分のプログラムを書き込むことがこの世界の常識です。

他の部分が何をやっているか理解できなかったとしても、我々はTODOと示されている部分に終了処理を記述することにしましょう。

TODOが2箇所あるのですが、今回は上のTODOの方に終了処理を記述します。仮に次のようにメッセージを表示するようにプログラムしておいてください。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

    Private disposedValue As Boolean = False ' 重複する呼び出しを検出するには

    ' IDisposable
   
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue
Then
           
If disposing Then
               
' TODO: 明示的に呼び出されたときにアンマネージ リソースを解放します
               
MsgBox("Disposeメソッドが呼び出されました!")
            End
If

       
' TODO: 共有のアンマネージ リソースを解放します
       
End If
       
Me.disposedValue = True
End Sub

#Region
" IDisposable Support "
   
' このコードは、破棄可能なパターンを正しく実装できるように Visual Basic によって追加されました。
   
Public Sub Dispose() Implements IDisposable.Dispose
       
' このコードを変更しないでください。クリーンアップ コードを上の Dispose(ByVal disposing As Boolean) に記述します。
       
Dispose(True)
        GC.SuppressFinalize(
Me)
    End
Sub
#End Region

■リスト17:DownloadCommanderクラス内のプログラム。Disposeメソッドのテスト。

さて、このコードですが今まで説明してきたNewコンストラクタやFinalizeデストラクタと違って自動的には呼び出されません。プログラマは明示的にDisposeメソッドを呼び出す必要があります。そのためフォーム側のプログラムは次のようになります。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim
Commander As New DownloadCommander("C:", "C:\DLLog.txt")

   
'▼伊藤博文
   
Commander.Download("http://www.kantei.go.jp/jp/rekidai/souri/images/souri01.jpg")

   
'▼黒田清隆
   
Commander.Download("http://www.kantei.go.jp/jp/rekidai/souri/images/souri02.jpg")

   
'▼山県有朋
   
Commander.FolderName = "C:\Test"
   
Commander.Download("http://www.kantei.go.jp/jp/rekidai/souri/images/souri03.jpg"

    Commander.Dispose()

End Sub

■リスト18:フォーム側のプログラム。Disposeメソッドの呼び出しを追加。

これでコードの形式上終了処理の部分は完了です。

ではDownloadCommanderクラスではどのような終了処理が必要か内容面について考えて見ましょう。終了処理は開いているファイルを閉じたり、ネットワークの通信を自動的に終了したりする目的で使用されます。

DownloadCommanderクラスではこのような処理は必要ないようにも思えます。終了処理が必要ないならば無理に面倒なデストラクタを実装する必要はありません。

ところがDownloadCommandクラスでも1つだけ終了処理で書くことがあるのです。話が複雑になるので気をつけてください。その1つだけ書くこととはWebClientクラスのDisposeメソッドを呼び出すことなのです。

WebClientクラスのメンバを良く見るとDisposeメソッドがあることがわかります。今説明したようにDisposeメソッドは明示的に呼び出さなければなりません。

実のところ今回の場合はWebClientクラスのDisposeメソッドは呼び出さなくても実害はないのですが、ここは将来もっと複雑なプログラムを書くときに備えてDisposeメソッドを呼び出すようにプログラムを改造しましょう。

先ほど、我々のDisposeメソッドの中に書いたメッセージの表示部分を削除して変わりにWebClientクラスのDisposeメソッドの呼び出しを記述しておいてください。

以上でDownloadCommanderは完成です。全コードは次の通りになります。

VB.NET2002対応 VB.NET2003対応 VB2005対応 VB2008対応

''' <summary>ファイルをダウンロードします。</summary>
Public Class DownloadCommander

   
Implements IDisposable

    Dim
wc As Net.WebClient = New Net.WebClient()

    Public
FolderName As
String
   
Public LogFileName As String

   
'■コンストラクタ
   
''' <param name="Folder">ダウンロードしたファイルの保存先フォルダ</param>
   
''' <param name="LogFile">ダウンロード履歴を記録するファイル</param>
   
Public Sub New(ByVal Folder As String, ByVal LogFile As String)

       
Me.FolderName = Folder
       
Me.LogFileName = LogFile

    End
Sub
    '■Download
   
''' <summary>ファイルをダウンロードします。</summary>
   
''' <param name="URL">対象のファイルのURL</param>
   
''' <remarks>ダウンロードしたファイルはFolderNameプロパティで指定したフォルダに保存されます。</remarks>
   
Public Sub Download(ByVal URL As String)

        Dim
FileName As
String

       
'▼ファイルのダウンロード
       
FileName = FolderName & "\" & IO.Path.GetFileName(URL)
        wc.DownloadFile(URL, FileName)

        '▼履歴をログに書き込む
       
Dim Writer As New IO.StreamWriter(LogFileName, True)
        Dim Description As
String

       
Description = Now.ToString("yyyy/MM/dd hh:mm:ss") & ", "
       
Description &= Environment.UserName & ", "
       
Description &= URL & ", "
       
Description &= FileName

        Writer.WriteLine(Description)
        Writer.Close()

    End
Sub
    Private disposedValue As Boolean = False ' 重複する呼び出しを検出するには

   
' IDisposable
   
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue
Then
           
If disposing Then
               
' TODO: 明示的に呼び出されたときにアンマネージ リソースを解放します
               
wc.Dispose()
            End
If
          
' TODO: 共有のアンマネージ リソースを解放します
       
End If
       
Me.disposedValue = True
   
End Sub

#Region
" IDisposable Support "
   
' このコードは、破棄可能なパターンを正しく実装できるように Visual Basic によって追加されました。
   
Public Sub Dispose() Implements IDisposable.Dispose
   
' このコードを変更しないでください。クリーンアップ コードを上の Dispose(ByVal disposing As Boolean) に記述します。
       
Dispose(True)
        GC.SuppressFinalize(
Me)
    End
Sub
#End Region

End
Class

■リスト19:DownloadCommanderクラスの全体

博士のワンポイントレッスン
V太:博士〜。結局よくわかりませんよ。Disposeメソッドってなんでこんなに面倒なんですか?NewとかFinalizeみたいに簡単に使えるようにすれば良いのに。マイクロソフトに言っておいてくださいよ。
博士:ふぉふぉふぉ。確かにDisposeメソッドにはちとややこしいことがあるのぉ。じゃがそれも理由あってのことなのじゃ。
その理由とは?!
その説明がまたややこしいのじゃ。概要だけ言うとプログラムの世界ではどのように終了処理を行うかということで専門化がずっと頭をひねっておるのじゃ。VBのDisposeメソッドはその1つの結論なのじゃ。
…と言われましても疑問は残ったままです。
ここでは説明しきれんので興味があれば「ガベージコレクション」や「Dispose Finalize パターン」をキーワードに調べてみて欲しい。