[VBA]中級講座2期受講メモ -Day3 クラス-

VBA

どうも。つじけ(tsujikenzo)です。こちらのシリーズでは2021年1月より受講しました「ノンプロ研VBA中級講座2期」で学んだことをアウトプットしております。本日はDay3です。

前回の講座

前回は「モジュール」ということで、特に「オブジェクトモジュール」について学びました。セッター・ゲッターの総まとめのような回になりましたね。VBAには5つのプロシージャがあることも整理できました。

(モジュールが、はたしてオブジェクトじゃないならなんなのかについてはまだモヤモヤですが💦)

[VBA]中級講座2期受講メモ -Day2 モジュール-
どうも。つじけ(tsujikenzo)です。こちらのシリーズでは2021年1月より受講しました「ノンプロ研VBA中級講座2期」で学んだことをアウトプットしております。本日はDay2です。前回の講座前回は「スコープとプロパティ」...

今日は「クラス」についてです。アジェンダはこのようになっております。

  1. クラスとインスタンス
  2. プロパティの定義
  3. メソッドの定義
  4. コンストラクタ・デストラクタ

クラスとインスタンス

「クラス」というのはオブジェクトを生成する機能のことをいいますが、「クラスとはなにか」という体系的なとらえ方や概念の説明は、別のブログ記事でご紹介したいと思います。まずは、「クラスの書き方、使い方」を徹底して身に付けましょう。

一番かんたんなクラスの作り方です。「挿入」メニューから「クラスモジュール」をクリックします。

プロパティウィンドウからクラス名をつけることができます。

オブジェクトブラウザを確認してみましょう。[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 Long
Ageプロパティに値を設定する際に、入力値に制限を掛けようという使い方ができます。

こちら、実行すると、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]
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
オブジェクトブラウザー内では「Property Letプロシージャのみ」で定義されているのか、「Property Getプロシージャ」もセットで定義されているのかがわかりませんね。プロパティを取得してみましょう。

クラスにメソッドを定義する

メソッドを追加する方法は、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ライブラリ」をお届けします。なんとか振り落とされずについていけてます。。。

このシリーズの目次

  1. [VBA]中級講座2期受講メモ -Day1 スコープとプロシージャ‐
  2. [VBA]中級講座2期受講メモ -Day2 モジュール-
  3. [VBA]中級講座2期受講メモ -Day3 クラス-
タイトルとURLをコピーしました