どうも。つじけ(tsujikenzo)です。こちらのシリーズでは2021年1月より受講しました「ノンプロ研VBA中級講座2期」で学んだことをアウトプットしております。本日はDay3です。
前回の講座
前回は「モジュール」ということで、特に「オブジェクトモジュール」について学びました。セッター・ゲッターの総まとめのような回になりましたね。VBAには5つのプロシージャがあることも整理できました。
(モジュールが、はたしてオブジェクトじゃないならなんなのかについてはまだモヤモヤですが💦)
今日は「クラス」についてです。アジェンダはこのようになっております。
- クラスとインスタンス
- プロパティの定義
- メソッドの定義
- コンストラクタ・デストラクタ
クラスとインスタンス
「クラス」というのはオブジェクトを生成する機能のことをいいますが、「クラスとはなにか」という体系的なとらえ方や概念の説明は、別のブログ記事でご紹介したいと思います。まずは、「クラスの書き方、使い方」を徹底して身に付けましょう。
一番かんたんなクラスの作り方です。「挿入」メニューから「クラスモジュール」をクリックします。
プロパティウィンドウからクラス名をつけることができます。
オブジェクトブラウザを確認してみましょう。[VBAProject]に[TestClass]ができています。[TestClass]はPrivate ClassでVBAProjectのメンバーということです。まだ中身は空っぽです。
クラスを作成しただけでは、オブジェクトとして定義しただけですので、まだ実体がありません(メモリアドレスが確保されていない)。実体化するということはオブジェクトを生成するということで、クラスからオブジェクトを生成することを「インスタンスを生成する」といいます。
クラス型の変数宣言とインスタンス化
クラスモジュール内で、このように記述します。「Dim宣言でクラスを定義して、SetステートメントでNewしてインスタンスを生成する」というのは、決まり文句のようなものなので、丸暗記してしまいましょう。
Dim 変数名 As クラス名 Set 変数名 = New クラス名
標準モジュールに以下のように記述して、ローカルウィンドウでインスタンスを確認してみましょう。
[Module] Sub MySub3_02() Dim test As TestClass Stop Set test = New TestClass Stop End Sub
1つめのSTOPのときは、変数testにクラスを定義しただけですので、値はNothingになっています。(型がTestClassになっていますね)
2つのめのSTOPでは、インスタンスを生成しましたので、空のオブジェクトが生成されています。(まだ変数testにはなにも値がありませんので<変数なし>と表示されています。)
ちなみに、
Dim test As Object
と型を宣言することも可能です。しかし、Object型は汎用性のあるなんでも格納することができる状態になってしまいますので、特に理由がないかぎり、
Dim test As TestClass
とし、型を厳密に構えるべきでしょう。
クラスにプロパティを定義する
プロパティの定義はDay1からずっとやってきました。以下の3つです。
- モジュールレベル変数(Public|Private)
- Property Let/Setプロシージャ
- Property Getプロシージャ
Public変数によるプロパティの定義
まず、Public変数を使ってプロパティを定義します。
[Class:Person] Public Name As String Public Age As Long [Module1] Sub MySub3_03() Dim myPerson As Person Set myPerson = New Person With myPerson .Name = "Bob" .Age = 25 Debug.Print .Name; "の年齢は"; .Age; "歳です" End With End Sub
[Module1]を実行すると、以下が出力されます。
Bobの年齢は 25 歳です
Stopによるオブジェクトの確認
ところどころ、STOPしてオブジェクトの状態を確認してみましょう。
まず、インスタンスを生成後にSTOPしてみます。
Dim myPerson As Person Set myPerson = New Person Stop
ローカルウィンドウを開くと「Person型のオブジェクトmyPerson」が生成されたことがわかります。そして、インスタンスを生成した時のプロパティ「Age」「Name」があり、中身には0や空文字の値が確認できます。
プロパティに値を定義した後でもStopしてみましょう。
With myPerson .Name = "Bob" .Age = 25 Stop Debug.Print .Name; "の年齢は"; .Age; "歳です" End With
ローカルウィンドウを開くと、myPersonオブジェクトにプロパティ[Age]と[Name]に値を格納できていることが確認できました。
Property Getプロシージャによるプロパティの定義
別名「読み込み専用プロパティ」として有名な、Propertry Getプロシージャによるプロパティ[IsAdult]の定義です。
今回は、自分が持っているAgeプロパティの判定をするプロパティ[IsAdult]を定義します。
[Class:Person] Public Name As String Public Age As Long Public Property Get IsAdult() As Boolean
IsAdult = False
If Age>= 18 Then IsAdult = True End Property [Module1] Sub MySub3_04() Dim myPerson As Person Set myPerson = New Person With myPerson .Age = 25 Debug.Print .IsAdult End With End Sub
サブプロシージャを実行すると、イミディエイトウィンドウにTrueが出力されます。
True
オブジェクトブラウザーで確認
ちょっと前後します(インスタンスを生成する前に定義できています)が、オブジェクトブラウザーを確認すると、Personクラスに「Age」「Name」そして、読み取り専用プロパティとして「IsAdult」が定義できています。
Property Let/Setプロシージャによるプロパティの定義
ちょっとおさらいですが、Property Letプロシージャでは、値を受け取ったときに、値の保管しておく場所として、プライベート変数を用意しなければなりません。
[Class:Person] Public Name As String Private age_ As Long 'Public Age As LongAgeプロパティに値を設定する際に、入力値に制限を掛けようという使い方ができます。
こちら、実行すると、Public Property Get プロシージャの[IsAdult]が読み込まれたときに、<プロパティの使い方が不正です。>と表示されます。
なぜでしょうか。
[Class:Person]
Public Name As String Private age_ As Long 'Public Age As Long Public Property Get IsAdult() As Boolean
IsAdult = False
If Age>= 18 Then IsAdult = True
End Property
Public Property Let Age(ByVal newAge As Long)
If newAge> 0 Then age_ = newAge Else age_ = 0
End Property
[Module1] Sub MySub3_05() Dim myPerson As Person Set myPerson = New Person myPerson.Age = 25 Stop End Sub
これは、IsAdultとしては、[Age] を参照したいわけですが、さきほどProperty Letプロシージャを定義するときに、プライベート変数化してしまったので、Ageが読み込めなくなったからです。
AgeはProperty Letプロシージャで定義されましたので、「プロパティの取得」ができるのみです。呼び出しができなくなってしまったのです。
これを解決するために、プロパティ[Age]を取得するための「ゲッター」が必要です。
[Class:Person]オブジェクトブラウザー内では「Property Letプロシージャのみ」で定義されているのか、「Property Getプロシージャ」もセットで定義されているのかがわかりませんね。プロパティを取得してみましょう。
Public Name As String Private age_ As Long 'Public Age As Long Public Property Get IsAdult() As Boolean
IsAdult = False
If Age>= 18 Then IsAdult = True
End Property
Public Property Let Age(ByVal newAge As Long)
If newAge> 0 Then age_ = newAge Else age_ = 0
End Property
Public Property Get Age() As Long Age = age_ End Property [Module1] Sub MySub3_06() Dim myPerson As Person Set myPerson = New Person myPerson.Age = 25 Debug.Print myPerson.Age Debug.Print myPerson.IsAdult End Sub
クラスにメソッドを定義する
メソッドを追加する方法は、Subプロシージャ/Functionプロシージャの2種類でした。
Subプロシージャ
特筆すべき点はありませんが、他のモジュール同様にSubプロシージャでメソッドを定義することができます。
[Class:Dog] Public Name As String Public Sub Greet() MsgBox "Bow!" End Sub [Module1] Sub MySub3_08() Dim myDog As Dog Set myDog = New Dog myDog.Greet End Sub
Functionプロシージャ
Functionプロシージャもとくに変わったことはありません。
[Class:Dog] Public Name As String Public Sub Greet() MsgBox "Bow!" End Sub Public Function Question() As String If MsgBox("Bow?", vbYesNo) = vbYes Then Question = "Bowwow!!" Else Question = "Woof..." End If End Function [Module1] Sub MySub3_09() Dim myDog As Dog Set myDog = New Dog Debug.Print myDog.Question End Sub
講座では、クラスに「自身のオブジェクトのコピーを生成するメソッド」を定義する課題がでました。使いどころがありそうなので、書いたコードを残しておきます。
'[class:Dog] Public Function Clone() As Object Public Name As String Set Clone = Me End Function '[Module1] Sub MySub3_10() Dim myDog As Dog Set myDog = New Dog Dim cloneDog As Object Set cloneDog = myDog.Clone cloneDog.Name = “taro” Stop
特殊(便利)なクラスの使い方
クラスには「インスタンスが生成されたとき」「インスタンスが使われなくなったとき」に呼び出されるメソッド(サブプロシージャ)を書くことができます。
なにかが動作したときに走るという「イベント」と呼ばれる機能です。(イベントについては別途記事にします)
書き方は以下のようになります。
'インスタンスが生成されたとき Private Sub Class_Initialize() End Sub 'インスタンスが使われなくなったとき Private Sub Class_Terminate() End Sub
これは手打ちしなくても、スニペットを呼び出す機能もありますのでご紹介します。
クラスモジュールの左上の[オブジェクトボックス]からクラス名をクリックします。
右上の[プロシージャボックス]から、選択するとスニペットがモジュールに追加されます。
たとえば、コードの実行時間を計測するコードはこのようになります。
Public Start As Date Public Finish As Date Private Sub Class_Initialize() Start = Time End Sub Private Sub Class_Terminate() Finish = Time MsgBox "実行時間は " & Format(Finish - Start, "nn分ss秒") & _ " でした", vbInformation + vbOKOnly End Sub Sub MySub3_12() Dim timerObj As TimerObject Set timerObj = New TimerObject Dim i As Long For i = 1 To 1000 ActiveCell.Value = "hoge" Next i 'Set timerObj = Nothing End Sub
最後になりますが、コンストラクタとは、インスタンスが生成されたときに最初に呼び出される関数です。デストラクタはインスタンスが破棄されたときに呼び出される関数です。
たまに、モジュールにの最後に「オブジェクト = Nothing」と書いて「Terminateしてますよ」と明記してるひともいます。丁寧な書き方ですね。参考にしましょう。
まとめ
さて、以上で「クラス」についてお伝えしました。ここまで「モジュール」「プロパティ」「クラス」の山を登って来たのは、さまざまな処理をすべて標準モジュールに書くと、スコープ(適用範囲)を含め、管理が大変になるからです。
日付に関する処理は「日付オブジェクトモジュール」を作ったり、シートに関するお決まりの処理は「シートオブジェクトモジュール」を作ったりして、オブジェクトモジュールとして管理・呼び出しをするわけです。
標準モジュールには、環境変数などの必要な情報を書くだけでよくなりますので、全体的にスッキリしますね。
次回は「Excelライブラリ」をお届けします。なんとか振り落とされずについていけてます。。。