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

 

Visual Basic 中学校 > 初級講座 >

第48回 高度なメソッド・プロパティ

いろいろな タイプのメソッド・プロパティの作成方法を説明します。省略可能な引数や可変引数、多重定義、共有メンバ、静的変数などさまざまなバリエーションのメンバの作成方法・使用方法が登場します。

概要

・呼び出し時に省略可能な引数を定義するにはOptionalを使う。

例:Public Sub Sample(Optional ByVal X As Integer = 2)

・呼び出し時に引数の数を可変で指定できるようにするにはParamArrayを使う。

例:Public Sub Sample(ByVal ParamArray Values() As Integer)

・名前が同じ複数のメソッド・プロパティを定義することができる。これを多重定義と呼ぶ。

例:

Public Overloads Sub Sample(ByVal X As Integer)

Public Overloads Sub Sample(ByVal X As Long)

・多重定義をするときにはOverloadsを指定するとプログラムがわかりやくするなる。

・共有メンバを定義するにはSharedを使う。

・適用範囲を離れても値が保存されている静的変数を定義するにはStaticを使う。

 

1.今回説明する内容

 

今回はメソッドやプロパティの作り方に関するやや高度な5つの事柄を説明します。メソッド・プロパティの基本的な作成方法については初級講座ですでに説明しています。もし、基本的なことをまだ理解していないようならそちらの記事をはじめに読んだほうが良いかもしれません。

リンク
クラスの使い方 第9回 クラスの使い方
メソッドの作り方の基本 第11回 メソッドを作る
プロパティの作り方の基本 第47回 プロパティの作成

今回紹介する5つの事柄を簡単にまとめておきます。

省略可能引数 Optional 指定しても指定しなくても良い引数を持ったメソッド・プロシージャの作成方法
可変引数 ParamArray 引数の数があらかじめ定まっていないメソッド・プロシージャの作成方法
多重定義 Overloads 名前が同じで引数が異なるメソッド・プロシージャの作成方法
共有メンバ Shared インスタンスを経由せずに直接使用できるメソッド・プロシージャの作成方法
静的変数 Static 適用範囲を離れても値を保つ変数を使ったメソッド・プロシージャの作成方法

■表1:今回の5つのテーマ

 

博士のワンポイントレッスン
B子 B子:今回はたくさんの事柄が出てくるのね。楽しみだわ。いろいろなタイプのメソッドやプロパティが作れるようになるのでしょう?
博士 博士:その通りじゃ。じゃがな、ちょっとボリュームが多くなっておるので一気に読んでもよく理解できんじゃろう。
V太 V太:どうしたらいいですか?
博士 博士:コードのサンプルを試しながらじっくり読むのがよいじゃろう。時間がなくて一気に読みたい場合はとりあえず理解できなくてもあまり気にしないでいつでも戻ってきて読み直すようにすればよいじゃろう。
V太 はい。わかりました。
博士 今回の内容は理解できなくても先に進めないという性質のものではないからの。とにかくあせらず、じっくりじゃよ。

 

 

2.省略可能引数

 

Optionalキーワード(読み方:Optional = オプショナル)を使用すると省略可能な引数を作ることができます。

たとえば、次のメソッドは挨拶を表示しますが、日本語・英語のどちらで表示するか引数で指定することができます。ただし、引数を省略した場合は日本語で表示します。

VB.NET 2002 対応 VB.NET 2003 対応 VB2005 対応

'■Hello
''' <summary>挨拶文を返します。</summary>
''' <param name="Language">0のとき日本語、その他の場合英語で挨拶文を作成します。</param>
''' <returns>挨拶文</returns>
Public Function Hello(Optional ByVal Language As Integer = 0) As String

    If Language = 0 Then
       
Return "こんにちは、みなさん!"
   
Else
       
Return "Hello, everybody!"
   
End If

End Function

■リスト1:Optionalを使った省略可能引数のシンプルな例

この例では引数LanguageOptionalがついているため 、メソッドの呼び出し時にこの引数を省略できるようになります。そして、引数の宣言の後ろに = 0 とあるのは省略された場合の既定値です。つまり、引数を省略した場合は0が指定されたものとみなされます。

このメソッドは通常通り引数を指定して呼び出すこともできます。

VB.NET 2002 対応 VB.NET 2003 対応 VB2005 対応

Dim St As String

St = Hello(0)
MsgBox(St)

■リスト2:省略可能引数といえども省略しないで指定することももちろん可能

引数を指定しないで次のように呼び出すこともできます。

VB.NET 2002 対応 VB.NET 2003 対応 VB2005 対応

Dim St As String

St = Hello()
MsgBox(St)

■リスト3:引数を省略して呼び出す例

引数が複数ある場合に、そのうちの1つの引数を省略可能にするときはそれより後ろにあるすべての引数を省略可能にしなければなりません。

VB6対応 VB.NET2002対応 VB.NET2003対応 VB2005対応

Public Sub Test(ByVal X As Integer, Optional ByVal Y As Integer = 100, Optional ByVal Z As Integer = 1)

End
Sub

■リスト4:複数の省略可能引数の例

上記の例では引数Yと引数Zは省略可能で、省略した場合はそれぞれ100と1が指定されたものとみなされます。ZOptionalをはずすとビルドエラーになります。Zより前にあるYが省略可能だからです。

なお、念のために確認しておきますがこの例でYを省略してZに値を指定する呼び出しかたは次のようになります。

VB6対応 VB.NET2002対応 VB.NET2003対応 VB2005対応


Call
Test(6, , 20)
 

■リスト5:途中の引数を省略して呼び出す例

 

発展学習  -  注意!Optional使用時の既定値は呼び出し側が保存します

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

簡単便利なOptionalではありますが、1つだけ重要な注意があります。引数を省略した場合にどのような既定値を採用するかはOptionalを定義している側に記述しますが、それがどのような値であったかは呼び出し側にひそかに記録されているのです。

このことをよく知っておかないと、複数のプロジェクトを使用している場合に後で既定値を変更しても変更した既定値が反映されなくなります。

たとえば、次のようなクラスを作ってdllを作成したとしましょう。dll名は仮にOptionalLibrary.dllとします。

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

Public Class Class1

    Public
Sub TestSub(Optional ByVal Message As String = "こんにちは")
        MsgBox(Message)
    End
Sub

End
Class

■リスト6:OptionalLibrary.dllのプログラム

そして、あるプロジェクトでこのdllを参照して、次のようにプログラムしたとしましょう。このプログラムは仮にOptionalTest.exeという名前でビルドされるものとします。

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

Dim C As New OptionalLibrary.Class1

C.TestSub()

■リスト7:OptionalTest.exeのプログラム

OptionalTest.exeを実行すると、もちろん「こんにちは」と表示されます。

次にdll側のプログラムで以下のように既定値を修正してビルドし直したとしましょう

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

Public Class Class1

    Public
Sub TestSub(Optional ByVal Message As String = "いただきます")
        MsgBox(Message)
    End
Sub

End
Class

■リスト8:OptionalLibrary.dllの修正版のプログラム

この状態でOptionalTest.exeを実行すると、「いただきます」ではなく「こんにちは」と表示されます。OptionalTest.exeが作成された時点での既定値が「こんにちは」だったからです。これが「既定値はOptional側に記述しますが、その値を保存しているのは呼び出し側です。」ということです。

この場合は、OptionalTest.exeをリビルドすると「いただきます」と表示されるようになります。

 

発展学習  -  C#とOptional

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

上記のようにVBでは手軽に扱える省略可能引数ですが、兄弟言語であるC#で扱うのはなかなか大変です。

まず、C#自体には省略可能引数というものが存在しないのでC#を使ってVBのOptionalと同じようなメソッドやプロパティを作成することはできません。ただし、後述するOverloadsを使った多重定義で代用することは可能です。

VBで作った省略可能引数をもったメソッドやプロパティをC#から呼び出すのもなかなか骨が折れます。この場合でもC#では引数を省略できないのです。

このような事情ですからC#との連携を前提としている場合はOptionalは使用しないで後述するOverloadsで代用するのが良いでしょう。C#とは無関係なプログラムをするのであれば便利なOptionalを思う存分使いましょう。

 

3.可変引数

 

いくつでも引数を受け入れるようにメソッド・プロシージャを作成することもできます。これにはParamArrayキーワード(読み方:ParamArray = パラムアレイ)を使用します。

この可変引数はたとえば、数値の平均値を求める場合などに便利です。普通に作っていたのでは数値が2つの場合、数値が3つの場合、4つの場合…とたくさんのメソッドを用意しなければなりませんがいくつ指定しても良いと言うことであれば1つ用意するだけで済みます。

平均値を求めるメソッドは次のようになります。

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

'■Average
''' <summary>平均値を求めます。</summary>
''' <param name="Values">平均を求める複数の数値</param>
''' <returns>平均値</returns>
''' <remarks></remarks>
Public Function Average(ByVal ParamArray Values() As Integer) As Double

    Dim Sum As Integer '合計値

   
For Each i As Integer In Values
        Sum += i
   
Next

    Return
Sum / Values.Length

End Function

■リスト9:平均値を求めるメソッド。可変引数の例。

ParamArrayを指定した引数はすべて配列になります。指定されたすべての引数は配列の要素になります。

このメソッドを使って平均値を求める呼び出し側のプログラムは次のようになります。

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

Dim Ave As Double

Ave = Average(100, 50, 70, 2, -50, 40)

MsgBox(Ave)

■リスト10:可変引数のメソッドの呼び出し例

 

4.多重定義1 型が違うだけで同じ機能

 

4−1.多重定義のケース

同じ名前で引数が違うメソッド・プロパティを複数作成することもできます。このことを多重定義、オーバーロードなどと言います。

なぜ同じ名前のメソッド・プロパティを複数作る必要があるのでしょうか。考えられる3つのケースを表にまとめてみます。

ケース 多重定義の例
型は違うけれども同じ機能のメソッド・プロパティがある場合(ジェネリックで代用可能な場合がある) Public Function Sample(X As Integer)
Public Function Sample(X As Long)
同じ内容だけだ形式が異なる引数を用意する場合 Public Function Sample(X As Integer, Y As Integer)
Public Function Sample(X As Point)
引数の指定を省略できるようにする場合
(Optionalを使用することも可能じ)
Public Function Sample(Name As String, Age As Integer)
Public Function Sample(Name As String)

■表2:多重定義のケース

1つ目のケースでは多重定義ではなくジェネリックを使用してメソッドやプロパティを作成できる場合もあります。ジェネリックはVB2005から導入された機能でいろいろな型を動的に扱う機能です。ジェネリックに関しては中級講座で扱います。3つ目のケースでは既に登場したOptionalを使用してメソッドやプロパティを作成することも可能です。

 

4−2.型が違う場合の多重定義の例

ここからはそれぞれのケースを順に詳しく見ていきます。

まずは型が違うだけで機能が同じメソッドの場合から紹介します。例として与えられた数値を2倍にするだけの小さなメソッドを考えて見ます。たとえば、次のようになります。

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

Public Function Twice(ByVal X As Integer) As Integer

    Return X * 2

End
Function

■リスト11:値を2倍にするシンプルなメソッド

これだけで一応完成ですが型がIntegerなのが気になります。つまりこのメソッドはLongDoubleDecimalの値を2倍にすることはできないのです。そこで、昔まだVBで多重定義ができなかったころは複数の型に対応するためにInteger用にはTwiceIntegerメソッドをつくり、Double用にはTwiceDoubleメソッドをつくりというように個別にメソッドを作っていました。

もっともObject型などの汎用的な型を使用する方法もありますが、この場合はパフォーマンス・メンテナンス性の面で不利になります。

今では名前のことは気にしないで素直にメソッドを書くことができますから特にこのようなことを気にする必要がありません。たとえば、このメソッドのDouble版は次の通りです。

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

Public Function Twice(ByVal X As Double) As Double

    Return X * 2

End
Function

■リスト12:上記の例で引数と戻り値がDouble型になったもの。

このDouble版を上記のInteger版と同じクラス内に作成してもエラーにはなりません。VBはメソッドやプロパティを区別するのに、その名前だけではなく、名前と引数の型、引数の順番を考慮しているのです。

ですから2つのTwiceメソッドは名前は同じだけれども引数の型が違うので別のメソッドとみなされるわけです。

なお、引数の名前は考慮されないので注意してください。引数の名前だけ違うメソッドを作ると同じメソッドとみなされてエラーになります。引数で重要なのは数と型と順番です。名前はVBにとってどうでもよいのです。

このメソッド・プロパティを特定する要素である「メソッド・プロパティの名前」、「引数の型」、「引数の順番」の組み合わせのことを「シグネチャ」と呼びます。エラーメッセージやドキュメントにはこの「シグネチャ」という言葉がしばしば登場して、「シグネチャが同じメンバは定義できない」などと表現されますので覚えておいてください。

また、シグネチャには戻り値の型は含まれていません。つまり戻り値の型だけしか違いがない場合は多重定義できません。

 

4−3.Overloadsキーワードの使用

さて、このように特に何も考えなくても素直に書いて行くことでいくらでも同じ名前のメソッドが作れるのですが、やはり同じ名前のメソッドが複数あるとプログラム時に混乱を生む場合があります。

そこで、少しでもこのような混乱を少なくするためにOverloadsキーワード(読み方:Overloads = オーバーロード)が用意されています。

Overloadsはメソッドまたはプロパティが多重定義されていることを示すキーワードで単なる目印に過ぎません。ただし、1つのメソッド・プロパティにOverloadsをつけた場合は、同じ名前 で多重定義されている他のメソッド・プロパティにもOverloadsをつける必要があります。

これで多少はプログラムに目印がついて可読性・メンテナンス性が向上しますので多重定義する場合はできるだけOverloadsをつけましょう。後で修正したときに思わぬバグを生む危険が低減されます。

これだけの意味しかない目印代わりのキーワードなんていらない気がしますか?でも、ちょっと想像してみてください。あなたはプログラムを修正しなければなりません。そのプログラムはとても長いプログラムです。あなたは修正する必要があるメソッドを発見して無事に修正を終えました。さて、ひょっとしてそのメソッドは多重定義されていないでしょうか?もし多重定義されていたら他にも修正しなければならない箇所があるかもしれません。このときOverloadsがついていれば他にもありますよという目印になりますよね。

上述のTwiceメソッドにOverloadsをつけると次のとおりになります。

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

Public Overloads Function Twice(ByVal X As Integer) As Integer

    Return X * 2

End
Function

Public Overloads Function Twice(ByVal X As Double) As Double

   
Return X * 2

End
Function

■リスト13:多重定義の例。2つのメソッドは名前が同じだが引数の型が異なるので多重定義できる。

 

4−4.呼び出し側のプログラム

多重定義されたメソッドの呼び出し側のプログラムも考えて見ます。次のコードを見てください。

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

Dim i As Integer = 100
Dim d As Double = 40

d = Twice(i)

MsgBox(d)

■リスト14:多重定義されたメソッドの呼び出し例

ここで呼び出しているTwiceメソッドがInteger版であるかDouble版であるかわかりますか?

答えはInteger版です。さきほど説明したようにVBはメソッドの名前と引数の型、引数の順番でメソッドを識別します。上記の呼び出しはTwiceという名前のメソッドをInteger型の1つの引数で呼び出していますからInteger版が呼び出されるわけです。戻り値はIntegerからDoubleに自動変換されます。

では、次の場合はどうでしょうか?

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

Dim dec1 As Decimal = 100
Dim dec2 As
Decimal

dec2 = Twice(dec1)

MsgBox(dec2)

■リスト15:多重定義されたメソッドの呼び出し例。自動型変換が行われる。

この場合は引数にDecimal型の値を渡していますからInteger版にもDouble版にも当てはまりません。結論から言うとこのような呼び出しを行った場合はDouble版が呼び出されます。これはVBができるだけ値を変更しないで型を変換しようとした結果です。

このような自動的な変換はわかりにくいので嫌われることが多いです。本当に自動的な変換をやめたいならばOption Strict Onを使用することになります。

さしあたって呼び出されるメソッドを明示的に制御したいのであれば明示的な型変換を行います。次の例では明示的な型変換を利用してInteger版のTwiceメソッドを呼び出します。

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

Dim dec1 As Decimal = 100
Dim dec2 As
Decimal

dec2 = Twice(CInt(dec1))

MsgBox(dec2)

■リスト16:明示的に型を指定して多重定義されたメソッドを呼び出す例。

 

5.多重定義2 同じ内容で違う形式の引数

 

もう1つの多重定義の場合として同じ内容だけれども違う形式の引数の場合を考えて見ましょう。

次のプログラムはフォームの左上の方にでっぱった四角形を描画します。

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

Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint

    Dim
Rect As Rectangle

    Rect.X = 10
    Rect.Y = 20
    Rect.Width = 100
    Rect.Height = 100

   
Call DrawEdge(e.Graphics, Rect)

End
Sub
'■DrawEdge
''' <summary>でっぱった四角形を描画します。</summary>
''' <param name="g">描画に使用するGraphicsクラスを指定します。</param>
''' <param name="Rect">描画する範囲を指定します。</param>
''' <remarks></remarks>
Private Sub DrawEdge(ByVal g As Graphics, ByVal Rect As Rectangle)

    ControlPaint.DrawButton(g, Rect, ButtonState.Normal)

End
Sub

■リスト17:フォームの左上の方にでっぱった四角形を描画する例

注目していただきたいのはDrawEdgeメソッドです。このメソッドには描画する範囲を指定するためにRectangle型の引数を用意しました。Rectangle構造体は四角形の座標を表現する構造体で、このように範囲を表す場合にはよく使用します。

しかし、描画する範囲を指定する方法はRectangle構造体だけではありません。Left, Top, Width, Heightの4つの値を使用してもできますし、左上と右下の2点を使用する方法もあります。 他にも何通りも考えられますが代表的な3つの方法を表にまとめてみます。

描画範囲を指定する方法 引数の形式
範囲を四角形で指定 1つのRectangle構造体
範囲の左上のX座標とY座標、および範囲の縦幅、横幅を指定 4つのInteger
範囲の左上の点と右上の点を指定 2つのPoint構造体

■表3:描画範囲の代表的な指定方法

どの方法でも引数の形式は異なりますが描画範囲の指定という内容は同じです。 このように同じ内容をいくつかの形式で指定できる場合にはメソッドを多重定義して好きな方式で指定できるようにしておくとその機能を使うプログラマには便利でしょう。その場その場にあった一番やりやすい形式を指定してよいと言うことですから柔軟なプログラムが可能になります。

さっそく例としてDrawEdgeメソッドの多重定義を紹介しましょう。

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

Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint

   
Call DrawEdge(e.Graphics, 10, 20, 100, 100)

End
Sub

'■DrawEdge
''' <summary>でっぱった四角形を描画します。</summary>
''' <param name="g">描画に使用するGraphicsクラスを指定します。</param>
''' <param name="Rect">描画する範囲を指定します。</param>
''' <remarks></remarks>
Private Overloads Sub DrawEdge(ByVal g As Graphics, ByVal Rect As Rectangle)

    ControlPaint.DrawButton(g, Rect, ButtonState.Normal)

End
Sub

'■DrawEdge
''' <summary>でっぱった四角形を描画します。</summary>
''' <param name="g">描画に使用するGraphicsクラスを指定します。</param>
''' <param name="Left">描画する範囲の横位置</param>
''' <param name="Top">描画する範囲の縦位置</param>
''' <param name="Width">描画する範囲の幅</param>
''' <param name="Height">描画する範囲の高さ</param>
''' <remarks></remarks>
Private Overloads Sub DrawEdge(ByVal g As Graphics, ByVal Left As Integer, ByVal Top As Integer, ByVal Width As Integer, ByVal Height As Integer)

    Dim
Rect As Rectangle

    Rect.X = Left
    Rect.Y = Top
    Rect.Width = Width
    Rect.Height = Height

   
Call DrawEdge(g, Rect)

End
Sub

'■DrawEdge
''' <summary>でっぱった四角形を描画します。</summary>
''' <param name="g">描画に使用するGraphicsクラスを指定します。</param>
''' <param name="Point1">描画する範囲の左上を表す点</param>
''' <param name="Point2">描画する範囲の右下を表す点</param>
''' <remarks></remarks>
Private Overloads Sub DrawEdge(ByVal g As Graphics, ByVal Point1 As Point, ByVal Point2 As Point)

    Dim
Rect As Rectangle

    Rect.X = Point1.X
    Rect.Y = Point1.Y
    Rect.Width = Point2.X
    Rect.Height = Point2.Y

   
Call DrawEdge(g, Rect)

End
Sub

■リスト18:DrawEdgeメソッドの多重定義

この例ではどのオーバーロード(多重定義されたメソッド)を呼び出しても実際に描画を行うのはRectangle版のDrawEdgeメソッドになるようになっています。

同じ機能で複数の多重定義を作成する場合はこのように実際の機能を持っているのは1つだけで、他のオーバーロードからはこのメソッドを呼び出すように構成するのが普通です。

このようにしないと同じ内容だけでちょっとずつ違うプログラムがあっちこっちに存在することになってしまい、どこかでバグは発生したり、修正する必要があったりする場合に大変です。

一箇所にまとめておけばバグであろうが機能変更であろうがその一箇所を修正するだけで済みます。

 

6.多重定義3 省略可能な引数の組み合わせ

 

多重定義の最後のパターンは省略可能な引数の組み合わせが複数存在する場合です。「省略可能な引数」というところがOptionalを使った場合とかぶっていて、実際のところOptionalでもオーバーロードでもどちらを使っても実現できると言う場合もあります。

さきほどのDrawEdgeメソッドにもう一度登場してもらいます。今度は背景色の指定ができるようにしてみましょう。オーバーロードがたくさんあると説明がやりにくいので前の例より少し省きます。

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

Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint

    Dim Rect As Rectangle

    Rect.X = 10
    Rect.Y = 20
    Rect.Width = 100
    Rect.Height = 100

    Call DrawEdge(e.Graphics, Rect, Color.CornflowerBlue)

End
Sub

'■DrawEdge
''' <summary>でっぱった四角形を描画します。</summary>
''' <param name="g">描画に使用するGraphicsクラスを指定します。</param>
''' <param name="Rect">描画する範囲を指定します。</param>
''' <remarks></remarks>
Private Overloads Sub DrawEdge(ByVal g As Graphics, ByVal Rect As Rectangle)

    Call
DrawEdge(g, Rect, Me.BackColor)

End
Sub

'■DrawEdge
''' <summary>でっぱった四角形を描画します。</summary>
''' <param name="g">描画に使用するGraphicsクラスを指定します。</param>
''' <param name="Rect">描画する範囲を指定します。</param>
''' <remarks></remarks>
Private Overloads Sub DrawEdge(ByVal g As Graphics, ByVal Rect As Rectangle, ByVal BackColor As Color)

    ControlPaint.DrawButton(g, Rect, ButtonState.Normal)
    Rect.Inflate(-2, -2)
    g.FillRectangle(
New SolidBrush(BackColor), Rect)

End Sub

■リスト19:多重定義を使った省略可能引数の表現例

この2つのオーバーロードは背景色を指定する引数があるかないかが異なります。背景色の引数がない方のオーバーロードを呼び出すと背景色には自動的にフォームの背景色(=Me.BackColor)が指定されます。同じことはOptionalを使ってもできます。

 

さて、このように多重定義にはいろいろなパターンがあり中には多重定義ではなくOptionalを使って代用できる場合もあります。

多重定義は便利な機能なのですがこのような事情から無計画にどんどんオーバーロードして行くとどこでなにをやっているのやら、どこがどこを呼び出しているのやら混乱してしまうことになります。多重定義はここぞというときに使うようにしましょう。

また、OverloadsOptionalを同時に使うとかなりわかりにくいプログラムになります。これは絶対やめておいた方がいいです。

 

7.共有メンバ

 

7−1.共有メンバの基本事項の確認

次に共有メンバの作成方法を説明します。共有メンバとはインスタンス経由ではなくクラスから直接よびだされるメソッドやプロパティのことです。たとえば、プログラムを開始した位置をフルパスで取得できるApplicationクラスのStartupPathプロパティは共有メンバなので次のようにインスタンスを経由せずに呼び出すことができます。

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

Dim Path As String

Path = Application.StartupPath

■リスト20:共有メンバの呼び出し例

もし、StartupPathプロパティが非共有メンバであれば必ずインスタンス経由で呼び出す必要があるので次のようにプログラムする必要があったでしょう。

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

'このコードは実際に動作しません。
Dim App As New Application
Dim Path As
String

Path = App.StartupPath

■リスト21:インスタンス経由での共有メンバの呼び出し例。Applicationクラスの制約により実際には動作しない。

 

7−2.共有メンバの作成

このような共有メンバを作成するのは簡単で、メソッドやプロパティを宣言するときにSharedキーワード(読み方:Shared = シェアード)を追加するだけです。

次の例では自作のクラスにWindowsフォルダのパスを表す読み取り専用のWindowsPathプロパティを記述します。

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

Public Class WindowsInformation

    ''' <summary>Windowsフォルダのパスを取得します。
</summary>
   
Public Shared ReadOnly Property WindowsPath() As String
       
Get

            Return
Environment.GetEnvironmentVariable("WINDIR")

        End
Get
   
End Property

End
Class

■リスト22;共有メンバの作成例

このプロパティはSharedで宣言されているので共有メンバです。ですから呼び出し側ではインスタンスを経由せずに次のように直接呼び出すことができます。

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

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

    Dim
Path As
String

   
Path = WindowsInformation.WindowsPath
    MsgBox(Path)

End
Sub

■リスト23:自作の共有メンバの呼び出し

 

7−3.共有メンバのインスタンス経由での呼び出しはやめましょう

共有メンバはインスタンス経由でも呼び出すことができますがインスタンス経由での呼び出しは推奨されておらず、VB2005では緑の波線で警告が表示されます。

VB2005を使っている方もそうでない方もためしにインスタンス経由でWindowsPathプロパティを呼び出すコードを記述してみてください。

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

Dim Info As New WindowsInformation
Dim Path As
String

Path = Info.WindowsPath

MsgBox(Path)

■リスト24:インスタンス経由での共有メンバの呼び出し

推奨されていないにもかかわらずこのコードが正常に動作することが確認できます。

この記述が推奨されていないのには2つの理由があります。1つは共有メンバがすべてのインスタンスで共有しているメンバであるにもかかわらずインスタンス経由で呼び出すということはあたかも特定のインスタンス専用のメンバのように見えてしまうという点です。インスタンス経由で呼び出しても共有メンバの動作が変わるわけではないのですがスタンスとして推奨されないということです。

もう1つの理由はちょっと小難しいので具体例で説明します。

まず、次の例を実行してコードが正常に動作するのを確認してください。

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

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

    Dim
Info As WindowsInformation
    Dim Path As
String

   
Info = GetInfo() 'インスタンスをメソッドから取得
   
Path = Info.WindowsPath

    MsgBox(Path)

End
Sub
Private Function GetInfo() As WindowsInformation

    MsgBox(
"GetInfoが呼び出されました。")
    Return New WindowsInformation

End
Function

■リスト25:メソッドの戻り値に由来するインスタンス経由での共有メンバの呼び出し

確認できたら、少し修正します。

Info = GetInfo()

Path = Info.WindowsPath

という記述は無駄なように思えるので、この部分を1行にまとめて次の通りにします。

Path = GetInfo.WindowsPath

つまり、全体では次のようになります。

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

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

    Dim
Info As WindowsInformation
    Dim Path As
String

   
Path = GetInfo.WindowsPath

    MsgBox(Path)

End
Sub
Private Function GetInfo() As WindowsInformation

    MsgBox(
"GetInfoが呼び出されました。")
    Return New WindowsInformation

End
Function

■リスト26:メソッドが実行されない注意すべき共有メンバの呼び出し例

これを実行すると驚くべきことに「GetInfoが呼び出されました。」というメッセージが表示されなくなります。つまりGetInfoメソッドを呼び出しているにもかかわらずGetInfoメソッドが実行されないのです。

これはインスタンス経由で呼び出そうがクラスから直接呼び出そうが共有メンバの動作は同じなので、プログラマがインスタンス経由で共有メンバを呼び出しているにもかかわらずVBが気を利かせて直接共有メンバを呼び出すために起こる現象です。

つまり、GetInfoメソッドを実行しなくてもWindowsPathプロパティの実行が可能であるためVBの最適化機能によってGetInfoメソッドの実行が省略されてしまったのです。

あまり良くあるケースではありませんが、このような事情があるため推奨どおりインスタンス経由での共有メンバの呼び出しはやめておいた方が無難です。

 

7−4.共有メンバ内部のプログラムの注意点

さて、共有メンバはSharedをつけるだけで簡単に作成できるのですが、共有メンバの内部のプログラムには少し非共有メンバのプログラムとは違う特徴があります。

共有メンバの内部ではインスタンスを経由しないと非共有メンバを呼び出せないのです。たとえば次のプログラムはエラーです。

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

Public Class WindowsInformation

    Public
Count As
Integer

   
''' <summary>Windowsフォルダのパスを取得します。</summary>
   
Public Shared ReadOnly Property WindowsPath() As String
       
Get

           
Count += 1 '←ここで非共有メンバを参照しているのでビルドエラーになります。
           
Return Environment.GetEnvironmentVariable("WINDIR")

        End
Get
    End Property

End
Class

■リスト27:共有メンバの内部で非共有メンバを呼び出そうとするとビルドエラーになる

これは予想通りの動作でしょう。非共有メンバはインスタンスの数だけ別々に存在するので共有メンバから非共有メンバを参照しようとしてもどのインスタンスの変数だか特定できないからです。

インスタンスを経由すれば共有メンバからでもどのインスタンスのメンバであるか特定できるので非共有メンバの呼び出しが可能ですが、ほとんどの場合意味がありません。インスタンスの非共有メンバを使用する必要があるならそのメンバは事実上共有メンバではないからです。

とは言え文法上はこのようなことが可能です。すぐには思いつきませんが何かのときに使うかもしれないので紹介しておきます。もう一度断っておきますがこの例は文法上は正しいですが何の意味もありません。

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

Public Class WindowsInformation

    Public Count As
Integer

   
''' <summary>Windowsフォルダのパスを取得します。</summary>
   
Public Shared ReadOnly Property WindowsPath() As String
       
Get

           
Dim Info As New WindowsInformation
            Info.Count += 1
'←意味はないがインスタンス経由なら非共有メンバを参照できる。
           
Return Environment.GetEnvironmentVariable("WINDIR")

        End
Get
    End Property

End
Class

■リスト28:共有メンバの内部でもインスタンス経由なら非共有メンバが参照できるがあまり意味はない。

このクラスの例で行くと強引にインスタンスを作るのではなくCount変数もSharedで宣言すればお互いに共有メンバということになるので参照可能です。こちらの方はよく使うでしょう。

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

Public Class WindowsInformation

    Public Shared Count As
Integer

   
''' <summary>Windowsフォルダのパスを取得します。</summary>
   
Public Shared ReadOnly Property WindowsPath() As String
       
Get

           
Count += 1
            Return Environment.GetEnvironmentVariable("WINDIR")

        End
Get
    End Property

End
Class

■リスト29:共有メンバの内部で共有メンバを呼び出すのは問題ない。

なお、非共有メンバから共有メンバの参照は自由に行うことができます。

 

8.静的変数

 

最後に静的変数を紹介します。静的変数は適用範囲から離れても値を保ち続ける変数です。

これもわかりにくいので具体例を挙げて説明します。

次のDescriberクラスのNextNumberメソッドの内部ではNumber変数が静的変数として宣言されています。静的変数を宣言するにはStaticキーワード(読み方:Static = スタティック)を使用します。

Number変数の値が保存されるためNextNumberメソッドは呼び出されるごとにカウントアップした値を返します。

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

Public Class Describer

    Public
Function NextNumber() As
Integer

       
Static Number As Integer

       
Number += 1
        Return Number

    End
Function

End
Class

■リスト30:静的変数の例。この例ではNumber変数が静的変数として宣言されている。

呼び出し例と次の通りです。

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

Dim Desc As New Describer

MsgBox(Desc.NextNumber)
'1
MsgBox(Desc.NextNumber) '2
MsgBox(Desc.NextNumber) '3

■リスト31:静的変数の値が保持されているのが確認できる

この例では順に「1」、「2」、「3」と表示されます。もし、Number変数がStaticではなく普通にDimで宣言されていたらこの呼び出しでは「1」、「1」、「1」と表示されることになります。

静的変数はメンバの内部にのみ記述できます。また、上記の例ではNumber変数は非共有メンバの内部に記述されているためインスタンスごとに異なる値を保持します。

共有メンバの中に静的変数を記述するとプログラムの実行が終了するまでずっと値を保持し続ける変数ということになります。

なお、上述の例はStaticを使って静的変数として宣言しなくてもクラスのメンバとして宣言すれば同じことが実現できます。

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

Public Class Describer

    Dim
Number As
Integer

   
Public Function NextNumber() As Integer

       
Number += 1
       
Return Number

    End
Function

End
Class

■リスト32:静的変数にしなくてもクラスレベルの変数にすれば値は保存される。

この例を改造してNumber変数をSharedで宣言すればNextNumberメソッドはすべてのインスタンスに共通してカウントアップを行う機能ということになります。

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

Public Class Describer

    Shared
Number As
Integer

   
Public Function NextNumber() As Integer

       
Number += 1
       
Return Number

    End
Function

End
Class

■リスト33:クラスレベルの共有メンバにすればすべてのインスタンス経由で同じ値を保存することになる。

呼び出し側のコードも改造して複数のインスタンスを通じてカウントアップを行っていることを確認してみてください。

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

Dim Desc1 As New Describer
Dim Desc2 As New Describer

MsgBox(Desc1.NextNumber)
'1
MsgBox(Desc1.NextNumber) '2
MsgBox(Desc1.NextNumber) '3

MsgBox(Desc2.NextNumber) '4
MsgBox(Desc2.NextNumber) '5
MsgBox(Desc2.NextNumber) '6

■リスト34:複数のインスタンス間でメンバ変数が共有され、値も保存されていることが確認できる。