Visual Basic 初級講座 |
Visual Basic 中学校 > 初級講座 >
いろいろなプロパティの作成方法を説明します。せっかくクラスを作ってもメソッドとプロパティの両方をうまく作らなくては使いにくいクラスになってしまいます。
概要 ・プロパティは主に状態を表すときに使用する。
・よくある基本的なプロパティの例
・読み取り専用プロパティを作成するにはReadOnlyキーワードを使用する。 ・既定のプロパティを設定するにはDefaultキーワードを使用する。 ・プロパティにも引数をつけることができる。 |
今回は独自のプロパティを作る方法を説明します。
細かい説明をする前に標準的なプロパティの例を紹介しておきます。以下の例は年齢をあらわすAgeプロパティの作成例です。
Private _Age
As
Integer Public Property Age() As Integer Get Return _Age End Get Set(ByVal value As Integer) _Age = value End Set End Property |
■リスト1:シンプルなプロパティプロシージャの例
このAgeプロパティは値を記録しておくだけで、他には何の機能もない極めてシンプルなプロパティです。
このコードをよく見てみると今までには登場しなかったような雰囲気が感じられます。プロパティの定義は確かに通常のコードとは少し雰囲気が違います。
たとえば、Public Property 〜 End Propertyの間にコードを記述すると言うのは今までに登場してきたIf 〜 End IfやSub 〜 End Subなどと同じですので違和感はありませんが、この中がさらにGet 〜 End Get(読み方:Get = ゲット)とSet 〜 End Set(読み方:Set = セット)に分割されているのは特徴的です。
詳しいことは少し後で説明します。理解を深めるためにここでは簡単にプロパティプロシージャについて説明しておきましょう。
プロパティに対しては「読み取り」と「書き込み」という2つの動作ができなければなりませんので、1つのプロパティプロシージャの中が読み取り時に作動するGet節と書き込み時に作動するSet節に分かれているのです。この点は単に値を返すだけのメソッドと異なります。言うなればメソッドは「読み取り」のみの動作しかなく、メソッドに対して値を書き込むということはないのです。
それから、値を記録しておく必要があるのでプロパティプロシージャの外に変数を1つ用意しておく必要があります。これもメソッドの場合と異なります。 上記の例では変数_Ageがこの変数です。変数の名前は好きにつけてよいのですが、Ageプロパティでセットした値を記録する変数と言うことで関連のありそうな名前をつけておく野が一般的です。
特にプロパティ名の前にアンダーバーをつけた名前が良く使われます。
ただし、ここで書いたのはあくまでも標準的なプロパティとメソッドの話です。外部の変数に値を保存しないプロパティもありえますし、外部の変数に値を保存するメソッドもありえます。プログラマが指令すればなんでも言うことを聞くのがVBです。
技術的な説明の前について「プロパティ」という考え方について説明します。
プロパティとはクラスの状態とか特性・性質と言った意味を持っています。よくあるTextプロパティは表示している文字の状態を表すプロパティですし、Visibleプロパティは可視状態を表すプロパティです。
プロパティと違ってメソッドはクラスの動作を意味します。
ただし、何がプロパティで何がメソッドか決定するのはあくまでもプログラマです。たとえば、Textプロパティでさえプロパティにする必然性はないのです。仮にTextプロパティをメソッドで表現するとしたらテキストをセットするためのSetTextメソッドとテキストを取得するためのGetTextメソッドを用意することになります。
しかしながらプログラマの気分によってあるものはプロパティ、あるものはメソッドと分けられたのでは使うほうは混乱してしまいますから、メソッドやプロパティを作成しようとするプログラマは自分が作ろうとしている機能は本当にメソッドにすべきなのか、それともプロパティにすべきなのか考えてから作るようにしましょう。
以前に実際にあったことなのですが、他人が作ったクラスを使っていたときのことを紹介します。そのクラスはデータをファイルに保存するクラスで、私は試しにそのクラスを使ってプログラムしていたのですが、肝心の保存を行うメソッドが見つからないのです。ファイルに保存する機能があるクラスならきっとSaveみたいな名前のメソッドがあると思ってしまいますよね?ところがSaveメソッドがないのです。
ひょっとするとこれを作ったプログラマは少し私と感性が異なっていて保存を行うメソッドにOutputとかExportとか言うような名前を使っているのかと思い、メソッドを1つずつ見ていったのですがとうとうそれらしいメソッドは見つかりませんでした。
しかし、保存する機能はあるはずです。いろいろ探した結果なんと保存を行うにはプロパティを使用すると言うことがわかりました。よくは覚えていないのですがたしか、MyData.Action = Output のような感じでActionプロパティに出力状態を表す定数をセットすると保存を開始すると言う仕様だったのです。
これってとてもわかりにくいと思いませんか?MyData.Saveの方がどう考えてもわかりやすいですよね?これがここで私の説明したいことです。
つまり、ある機能をメソッドにするかプロパティにするかはプログラマの自由ですが、どちらにするかよく検討しないで作ると使う側から見て使いにくいクラスができてしまうのです。
プロパティは状態、メソッドは動作です。プロパティは静、メソッドは動と言ってもよいでしょう。
このことも含めてプロパティとメソッドの違いを表にまとめておきます。
プロパティ | メソッド | |
考え方 | クラスの状態 | クラスの動作 |
キーワード | Property | Sub, Function |
読み取り | ○ | ○ |
書き込み | ○ | × |
詳細な動作のプログラム | ○ | ○ |
■表1:プロパティとメソッド
プロパティと似たようなものにはメソッドの他にフィールドがあります。 フィールドはクラスで宣言されている単なる変数のことです。単なる変数なので値を記録しておく以外には何の機能もありませんが、値を記録しておくだけのプロパティを作成するのであればフィールドとして作成したほうが楽な場合もあります。
ただし、フィールドとして作成していしまうとプロパティに許されている便利なVBの機能が使えなくなってしまいます。 また、フィールドもプロパティもパフォーマンス上は同等ですので、通常はフィールドではなくプロパティを使用するようにしてください。
フィールドの宣言はそのまま変数の宣言なのでクラス内で次のように記述するとこれだけでAgeフィールドが完成します。
Public Age As Integer |
■リスト2:フィールドの例
フィールドは外部に公開するためにはPublicで宣言します。DimやPrivateなどでの宣言は他のプロパティやメソッドから間接的にアクセスするプライベートなフィールドということでよく使いますが、外部から直接アクセスできなくなってしまいますのでプロパティの代わりに使うものとしては意味がありません。
なお、定数もフィールドになります。
プロパティとフィールドの違いを表にまとめておきます。
プロパティ | フィールド | |
考え方 | クラスの状態 | クラスの項目 |
キーワード | Property | Public (単なる変数・定数と同じ) |
読み取り | ○ | ○ |
書き込み | ○ | ○ |
詳細な動作のプログラム | ○ | × |
■表2:プロパティとフィールド
プロパティと似ているけれどもプロパティとは異なるメソッドとフィールドについて説明したことで、「プロパティ」の意味と機能がはっきりしてきたことと思います。
ここからはいよいよ具体的な構文を交えてプロパティを作成する方法を説明します。
実際にプログラムをしながら説明の内容を確認していってください。プロジェクトを1つ作ってクラスを新しく追加してください。このクラスは何かの会員を表すクラス で、名前はMemberとします。
まずはMemberクラスに誕生日を表現するためのBirthDayプロパティを作成します。とりあえず何の機能もない空のBirthDayプロパティを作成すると次の通りとなります。これは空のプロパティプロシージャなのでまさにプロパティプロシージャの構造が シンプルに表現されています。
Public
Class Member Public Property BirthDay() As Date Get 'ここにプロパティから値を読み取る場合のコードを書く End Get Set(ByVal value As Date) 'ここにプロパティに値を書き込む場合のコードを書く End Set End Property End Class |
■リスト3:クラス側のプログラム。空のプロパティを作ったところ。
この構造は読み取りと書き込みの両方ができるプロパティの場合です。読み取り専用または書き込み専用のプロパティの作り方は後で説明します。
この例ではGet節にもSet節にも何もプログラムがないので、まったく空のプロパティを作っていることになります。このBirthDayプロパティには値を保存する機能すらありません。
試しにフォーム側でこのクラスを使って次の通りにプログラムしてみてください。
Private
Sub Button1_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
Button1.Click Dim ThisMember As New Member ThisMember.BirthDay = #6/27/1992# MsgBox(ThisMember.BirthDay) End Sub |
■リスト4:フォーム側のプログラム。この段階では予期した通りには動作しない。
BirthDayに1992/6/27をセットしたすぐ後でBirthDayプロパティの値を表示するようにしているのですが、実行すると0:00:00が表示されます。BirthDayプロパティに値を保存する機能すらない証拠です。
値を保存する機能をプログラムする前にプロパティプロシージャをもう少しいじって実験してみます。次のように改造してください。
Public
Class Member Public Property BirthDay() As Date Get MsgBox("BirthDayプロパティの値を読み取ろうとしました。") End Get Set(ByVal value As Date) MsgBox("BirthDayプロパティに値を書き込もうとしました。") End Set End Property End Class |
■リスト5:クラス側のプログラム。実験の例で実際の機能はない。
これで先ほどと同じプログラムを試してみると、プロパティプロシージャのGet節とSet節がうまく呼び出されていることがわかります。
ここまで仕組みがわかれば値を保存するためにどのようにプログラムすればよいか理解できそうです。値を保存する機能を追加すると次のようになります。
Public
Class Member Dim _BirthDay As Date Public Property BirthDay() As Date Get Return _BirthDay End Get Set(ByVal value As Date) _BirthDay = value End Set End Property End Class |
■リスト6:クラス側のプログラム。
ここまでの説明で、値を記録するだけのシンプルなプロパティの作成の仕方と動作の仕組みがわかっていただけたと思います。
誕生日を記録するプロパティができたところで、今度は年齢をあらわすAgeプロパティで作ってみましょう。AgeプロパティもBirthDayプロパティと同じように作成して読み書きできるようしてもいいのですが、誕生日がわかっていれば年齢も自動的に決定されるのでAgeプロパティは読み取り専用のプロパティとします。
読み取り専用のプロパティを作成するにはプロパティプロシージャの宣言にReadOnlyキーワード(読み方:ReadOnly = リードオンリー)を加えて、値を書き込むためのSet節を省きます。
Ageプロパティは次のようになります。
Public
ReadOnly
Property Age()
As
Integer Get '例:BirthDay = 2001/06/27, Now = 2006/06/30の場合 'intBirthDay = 20010627, intNow = 20060630 'intNow - intBirthDay = 50003 '50003 ÷10000 = 5.0003 'ゆえに、この人物は5歳 Dim intBirthDay As Integer = CInt(BirthDay.ToString("yyyyMMdd")) Dim intNow As Integer = CInt(Now.ToString("yyyyMMdd")) Return (intNow - intBirthDay) \ 10000 End Get End Property |
■リスト7:クラス側のプログラム。読み取り専用プロパティの例。
年齢を計算するロジックは今回説明するテーマではありません。コメントを参考にしてください。
プロパティプロシージャとして注目すべき点はGet節では単に変数に記録されている値を返すだけではなく、計算を行っている点です。このようにGet節、Set節ともに通常のVBのプロシージャと同様に好きなようにプログラムを実行させることができます。
ところで、あまり使用しませんがプロパティを書き込み専用にすることもできます。この場合はWriteOnlyキーワード(読み方:WriteOnly = ライトオンリー)を使用し、Get節を省きます。
さて、今度は履歴書の履歴を管理する簡単なクラスを使っていろいろなタイプのプロパティを説明します。
まず、プロパティの型にはいろいろなクラスを自由に指定できることを紹介します。このことはわざわざ指摘しなくても当然のことなのですが、型を指定するときになんとなくIntegerやStringなど基本的な型でなくてはいけないような気がしてしまう方も結構いらっしゃるようです。
プロパティの型にコレクションを指定するとクラスの利用者側で便利に使うことが可能です。
まずは履歴クラスのシンプルなコード例を使って考えて見ましょう。
'■PersonalHistory ''' <summary>履歴を編集・管理します。</summary> Public Class PersonalHistory Private _Histories As New ArrayList '■Histories ''' <summary>それぞれの履歴を表すコレクション</summary> Public ReadOnly Property Histories() As ArrayList Get Return _Histories End Get End Property |
'■HistoryMemo ''' <summary>全履歴を取得します。</summary> Public ReadOnly Property HistoryMemo() As String Get Dim Item As String Dim Result As String = "" For Each Item In _Histories Result &= Item & vbNewLine Next Return Result End Get End Property End Class |
■リスト8:クラス側のプログラム。
このクラスではHistoriesプロパティの型がArrayListになっていることと、HistoriesプロパティにReadOnlyつまり読み取り専用が指定されていることに注意してください。
メモ -
VB2005の場合はジェネリックも使用できます 上記の例はVB.NET2002, VB.NET2003, VB2005のどれにも当てはまります。VB2005の場合はArrayListではなくジェネリックを使用してコレクションの型を指定することができるので利用した方が良いでしょう。 ジェネリックを使用するにはArrayListの箇所をすべてList(Of String)に訂正します。 |
このクラスの利用例を以下に示します。
Dim
Rireki As New
PersonalHistory Rireki.Histories.Add("1983年月犬上市立第2犬上小学校入学") Rireki.Histories.Add("1989年月犬上市立第2犬上小学校卒業") Rireki.Histories.Add("1989年月犬上市立二日中学校入学") Rireki.Histories.Add("1990年月英検3級取得") 'TextBox1はMultiLine=Trueにして大きめに配置しくおくと良い。 TextBox1.Text = Rireki.HistoryMemo |
■リスト9:フォーム側のプログラム。
HistoriesプロパティがArrayList型なので、このクラスの使用者はHistoriesプロパティからさらにArrayListクラスのメンバを呼び出すことができます。上記の例ではAddメソッドを呼び出してArrayListに値を追加しています。
また、Historiesプロパティは読み取り専用であるにもかかわらずArrayListのAddメソッドを呼び出して値を追加することができるのが妙に感じられるかもしれません。
プロパティプロシージャにReadOnlyを指定した場合の動作は、「その値を変更できない」と言うよりは「その値をセットできない」と考えたほうが実際に近いのです。
ReadOnlyをつけても上記の例のようにメソッドやプロパティを呼び出してそのプロパティの状態をどんどん変更することは可能です。しかし、プロパティの値そのものをセットしようとするとエラーになります。たとえば次のコードはエラーです。
Dim
Ar As New
ArrayList Dim Rireki As New PersonalHistory Ar.Add("1983年月犬上市立第2犬上小学校入学") Ar.Add("1989年月犬上市立第2犬上小学校卒業") Ar.Add("1989年月犬上市立二日中学校入学") Ar.Add("1990年月英検3級取得") Rireki.Histories = Ar '←ここでビルドエラーが発生します。 'TextBox1はMultiLine=Trueにして大きめに配置しくおくと良い。 TextBox1.Text = Rireki.HistoryMemo |
■リスト10:フォーム側のプログラム。ビルドエラーになる例。
さきほどの例はプロパティの値そのものをセットしているわけではなくプロパティのメンバを呼び出していただけなのに対し、この例ではプロパティの値そのものをセットしようとしているのでReadOnlyに違反してビルドエラーとなるのです。
このような動作ですからプロパティの型にクラスを指定する場合はReadOnlyをつけることが少なくありません。
メモ - でも、構造体はだめです。 プロパティの型には構造体も指定できますが、利用する方法は少し異なります。 たとえば、フォームの位置を表すLocationプロパティはPoint構造体です。そしてこのプロパティに対してクラス感覚で次のようにプログラムすると「Expression は値であるため、代入式のターゲットにすることはできません。」と表示されてビルドエラーになります。
■リスト11:ビルドエラーになる。 これはクラスは参照型で構造体が値型であることに起因する違いです。このような書き方をした場合Location.XプロパティはLocationプロパティの暗黙的に作成されているコピーのXプロパティを指しているので値を代入しても意味がなく、親切なことにビルドエラーになります。(もしビルドエラーにならなかったらこのことに気がつかないでプログラムが完成したと思い込んでしまうプログラマがたくさん出現することでしょう。) 上記のようなプログラムを行いたい場合は次のようにします。
■リスト12:正常に動作する |
さて、このクラスですが履歴を追加するときにRireki.Histories.Addと書くのはどうも違和感がありませんか?やはりRireki.Addとシンプルに書きたいですよね。
この場合はPersonalHistoryクラスでAddメソッドを定義して、コレクションは内部で持つことになります。
'■PersonalHistory ''' <summary>履歴を編集・管理します。</summary> Public Class PersonalHistory Private _Histories As New ArrayList '■Add ''' <summary>履歴を追加します。</summary> ''' <param name="Item">履歴</param> Public Sub Add(ByVal Item As String) _Histories.Add(Item) End Sub |
'■HistoryMemo ''' <summary>全履歴を取得します。</summary> Public ReadOnly Property HistoryMemo() As String Get Dim Item As String Dim Result As String = "" For Each Item In _Histories Result &= Item & vbNewLine Next Return Result End Get End Property End Class |
■リスト13:クラス側のプログラム。
でも、こうやると履歴の追加はできるのに個々の履歴にアクセスできなくなってしまいます。そこで、個々の履歴にアクセスするためのItemプロパティも追加してみましょう。
'■Item ''' <summary>Addメソッドで追加した個々の履歴を取得または設定します。</summary> ''' <param name="Index">履歴の番号。履歴を追加した順に0から始まる番号です。</param> ''' <value>新しい履歴の内容</value> Public Property Item(ByVal Index As Integer) As String Get Return _Histories(Index) End Get Set(ByVal value As String) _Histories(Index) = value End Set End Property |
■リスト14:クラス側のプログラム。
このItemプロパティは今までにいくつか紹介したプロパティプロシージャと違ってIndexという引数がついていることに注意してください。履歴は複数登録できるので履歴を識別するための番号が必要なのです。そうでないと要求された履歴がいつ追加したどの履歴なのかわかりようがありません。
この番号はAddメソッドで変数_Historiesに文字列を追加したときにArrayListクラスの機能で自動的につけられる番号なので今回は自分で管理する必要はありません。受け取った番号をそのまま変数_Historiesに渡すだけです。
このようにプロパティにもメソッド同様に引数をつけることができます。もちろん必要とあれば2つ以上の引数を追加することもできます。
ここまで書けばクラスの使用者は次のように自由自在に履歴を操るプログラムを書くことができます。
Dim
Rireki As New
PersonalHistory Rireki.Add("1983年月犬上市立第2犬上小学校入学") Rireki.Add("1989年月犬上市立第2犬上小学校卒業") Rireki.Add("1989年月犬上市立二日中学校入学") Rireki.Add("1990年月英検3級取得") '2番目に追加した履歴の表示 MsgBox("2番目に追加した履歴は→" & Rireki.Item(1)) '3番目に追加した履歴の修正:「犬上市立」を「私立」に訂正 Rireki.Item(2) = "1989年月私立二日中学校入学" 'TextBox1はMultiLine=Trueにして大きめに配置しくおくと良い。 TextBox1.Text = Rireki.HistoryMemo |
■リスト15:フォーム側のプログラム。個々の履歴には自在にアクセスできる。
実のところ履歴を削除する機能がないのですが、それは自分でいろいろ試してみてください。
ところで、上記のプログラムでRireki.Item(2) =と書いている部分にみなさんは違和感を感じませんか?
機能としてはこれでよいのですが、VBにはじめから用意されているいろいろなクラスではこのような場面ではRireki(2) = のような書き方ができるようになっています。
この.Itemを省略できるようにしたいというわけです。
省略可能なプロパティは「既定のプロパティ」と呼ばれ、1つのクラスに1つだけ指定することができます。
指定するのはとても簡単で、プロパティの宣言にDefaultキーワード(読み方:Default = デフォルト)を追加するだけです。
'■Item ''' <summary>Addメソッドで追加した個々の履歴を取得または設定します。</summary> ''' <param name="Index">履歴の番号。履歴を追加した順に0から始まる番号です。</param> ''' <value>新しい履歴の内容</value> Default Public Property Item(ByVal Index As Integer) As String Get Return _Histories(Index) End Get Set(ByVal value As String) _Histories(Index) = value End Set End Property |
■リスト16:クラス側のプログラム。Itemプロパティを既定のプロパティに設定。
なお、既定のプロパティにできるのは引数付きのプロパティだけです。
博士のワンポイントレッスン
|
これで、そこそこに使いやすい履歴クラスができました。需要がないので実際に使われることはないでしょうがプロパティの勉強用にはちょうど良かったと思います。
最後に念のためにこのクラスとそれを利用するフォームの全体のプログラムを掲載しておきます。
クラス側
'■PersonalHistory ''' <summary>履歴を編集・管理します。</summary> Public Class PersonalHistory Private _Histories As New ArrayList '■Item ''' <summary>Addメソッドで追加した個々の履歴を取得または設定します。</summary> ''' <param name="Index">履歴の番号。履歴を追加した順に0から始まる番号です。</param> ''' <value>新しい履歴の内容</value> Default Public Property Item(ByVal Index As Integer) As String Get Return _Histories(Index) End Get Set(ByVal value As String) _Histories(Index) = value End Set End Property |
'■Add ''' <summary>履歴を追加します。</summary> ''' <param name="Item">履歴</param> Public Sub Add(ByVal Item As String) _Histories.Add(Item) End Sub |
'■HistoryMemo ''' <summary>全履歴を取得します。</summary> Public ReadOnly Property HistoryMemo() As String Get Dim Item As String Dim Result As String = "" For Each Item In _Histories Result &= Item & vbNewLine Next Return Result End Get End Property End Class |
■リスト17:クラス側のプログラム
フォーム側
Private
Sub Button1_Click(ByVal
sender As System.Object,
ByVal e As
System.EventArgs) Handles Button1.Click Dim Rireki As New PersonalHistory Rireki.Add("1983年月犬上市立第2犬上小学校入学") Rireki.Add("1989年月犬上市立第2犬上小学校卒業") Rireki.Add("1989年月犬上市立二日中学校入学") Rireki.Add("1990年月英検3級取得") '2番目に追加した履歴の表示 MsgBox("2番目に追加した履歴は→" & Rireki.Item(1)) '3番目に追加した履歴の修正:「犬上市立」を「私立」に訂正 Rireki(2) = "1989年月私立二日中学校入学" 'TextBox1はMultiLine=Trueにして大きめに配置しくおくと良い。 TextBox1.Text = Rireki.HistoryMemo End Sub |
■リスト18:フォーム側のプログラム
発展学習 -
コレクションを自作クラスで表現する方法 今回は内部でArrayList型の変数を持ってコレクションのように扱えるクラスを作成しましたが、実際のコレクションならば当然もっているいくつかの機能が欠けています。 たとえば、For Eachで値を列挙できるのがコレクションの特徴ですが今回のクラスではそれができませんし、その他にもコレクションを要求されている場所で今回のクラスを指定することができないなどかなりの不便があります。 実際に自作のコレクションを作る場合には、既存のコレクションを継承して作ると楽です。継承を使用した場合は普通のコレクションが持っている機能は何もプログラムしなくてもすべて利用できます。 継承して作成した場合の一例を紹介します。
■リスト19:クラス側のプログラム。継承を利用した例。 これだけのコードで本文中で紹介したクラスの機能 + 通常のコレクションとしての機能があります。継承については中級講座で扱う予定です。 |