VBAでクラスをつくる

VBAでのクラスの作り方です。

2019/10/12: 一部文言等、修正しました。

##クラスモジュールを挿入する
![image.png](https://qiita-image-store.s3.amazonaws.com/0/133716/46e5acd9-38df-5d6b-57da-2b5bde68dd44.png)
こんな感じでクラスモジュールを挿入します。

###クラスの名前を付ける
![image.png](https://qiita-image-store.s3.amazonaws.com/0/133716/238ae176-ce89-3d56-6d4d-c6bae3221ad0.png)

プロパティウィンドウの「（オブジェクト名）」の欄でクラスの名称を設定します。

これで空のクラスができました。

###メンバ変数を定義する
クラスのメンバ変数を定義してみます。

```vbnet:Person(クラスモジュール)
Option Explicit

' メンバ変数
Public Name As String
Private Age As Long
```
``Private``とつけたものはクラスの外からはアクセスできません。
``Public``は外からアクセスできます。
（アクセス修飾子といいます）

###メソッドを作る
メソッドを追加した例です。

```vbnet:Person(クラスモジュール)
Option Explicit

' メンバ変数
Public Name As String
Private Age As Long
Public Mother As Person

' メソッド
Public Sub SayHello()
    MsgBox "Hello, I'm " & Name & "!"
End Sub

' 引数付き
Public Sub SayHelloTo(otherPersonName As String)
    MsgBox "Hello, " & otherPersonName & "!" & vbNewLine & _
        "I'm " & Name & "!"
End Sub

' 戻り値あり（関数）
Public Function GetAge() As Long
    GetAge = Age
End Function

' オブジェクトを返す関数（Setをつけること！）
Public Function GetMother() As Person
    Set GetMother = Mother
End Function
```

メンバ変数と同様、頭に``Public``をつけると外からアクセスでき、``Private``ではアクセスできません。

オブジェクト型を返す時は ``Set`` をつける必要があります。

###``Me``キーワード
``Me``はクラスモジュールでは自身のインスタンスを指します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/133716/283eed01-bba9-8431-6b86-42aab03ac5e7.png)

###InitializeとTerminate
``Class_Initialize``はクラスのインスタンスが生成されたときに呼ばれます。引数はつけられません。
``Class_Terminate``はクラスのインスタンスが破棄されたときに呼ばれます。
ただし、ガーベージコレクションが動くタイミングは必ずしも分からないので、リソース（ex. DBへの接続など）を解放するメソッドを別に作るのが良いといわれています。

```VB.NET:Person(クラスモジュール)追加分
' 初期化処理
Private Sub Class_Initialize()
    Name = "Bob"
    Age = 30
    Set Mother = Nothing
    
    Debug.Print "initalized"
End Sub

' 終了時処理
Private Sub Class_Terminate()
    Name = ""
    Age = 0
    Set Mother = Nothing
    
    ' その他、使用しているリソースの解放処理を行う
    '
    
    Debug.Print "terminated"
End Sub
```
これらのメソッドは``Private``にします。``Public``にするとintellisense(コード補完)で見えてしまいます。

###クラスの使い方
クラスの使い方です。

```vbnet:標準モジュール
Option Explicit

Sub test1()
    ' オブジェクト型の変数を宣言
    Dim p As Person
    
    ' インスタンス生成
    Set p = New Person
    
    ' 値をセットする
    p.Name = "Bob"
    
    ' 値を取得してみる
    Debug.Print "age: " & p.GetAge()
    
    ' メソッド呼び出し
    Call p.SayHello
    
    ' オブジェクト破棄（スコープから出るとガーベージコレクションによって勝手に破棄されます）
    Set p = Nothing
End Sub
```
####結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/133716/e0ddfda9-1958-bf2b-0db3-bb3f0570daf4.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/133716/3c3ef3f4-11dd-cace-3248-50940311479e.png)

###プロパティ プロシージャを使う
先ほどの例ではメンバ変数``Name``に外部から直接値を設定していましたが、それだと例えば空文字列ならエラーを出すといった処理ができません。
メンバ変数の設定時や取得時に何か処理をするには、プロパティ プロシージャを使います。

```VB.NET:Person(クラスモジュール)
Option Explicit

' メンバ変数
' NameとMotherをPrivateにした
Private Name As String
Private Age As Long
Private Mother As Person

' 初期化処理
Private Sub Class_Initialize()
    Name = "Bob"
    Age = 30
    Set Mother = Nothing
    
    Debug.Print "initalized"
End Sub

' 終了時処理
Private Sub Class_Terminate()
    Name = ""
    Age = 0
    Set Mother = Nothing
    
    ' その他、使用しているリソースの解放処理を行う
    '
    
    Debug.Print "terminated"
End Sub

' メソッド
Public Sub SayHello()
    MsgBox "Hello, I'm " & Name & "!"
End Sub

' 引数付き
Public Function SayHelloTo(otherPersonName As String) As Person
    MsgBox "Hello, " & otherPersonName & "!" & vbNewLine & _
        "I'm " & Name & "!"
End Function

' プロパティプロシージャ
Property Get MyName() As String
    MyName = Name
End Property

Property Let MyName(namae As String)
    If namae = "" Then
        ' 氏名がブランクならエラー
        Err.Raise 10000, , "名前がブランクです"
    End If
    
    Name = namae
End Property

Property Get MyAge() As Long
    ' 5歳さばを読んでみる
    MyAge = Age - 5
End Property

Property Get MyMother() As Person
    Set MyMother = Mother
End Property

Property Set MyMother(haha As Person)
    Set Mother = haha
End Property
```
値を設定するときは、値型ならばLet、オブジェクト型ならばSetを使います。値の取得は、どちらもGetです。
なお、ここで``MyAge``は``Get``のプロパティ プロシージャしかないので、readonlyプロパティとか言ったりすることもあります。

使い方です。

```VB.NET:標準モジュール
Option Explicit

Sub test1()
    Dim p As Person
    
    ' インスタンス生成
    Set p = New Person
    
    ' 値をセットする
    p.MyName = "Bob"
    
    ' オブジェクト型をセットする
    Dim mom As Person
    Set mom = New Person
    Set p.MyMother = mom
    
    ' 値を取得してみる
    Debug.Print "age: " & p.MyAge
    
    ' メソッド呼び出し
    Call p.SayHello
    
    ' オブジェクト破棄（スコープから出るとガーベージコレクションによって勝手に破棄されます）
    Set p = Nothing
End Sub
```

####結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/133716/f7d645b6-22b6-4ecb-00b2-0f4c0fdd062e.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/133716/b1494b89-5fdb-f8c9-c246-e3bb0217b9a8.png)

###Nothing
オブジェクト型で変数を宣言した後、初期状態だと``Nothing``が変数に入っています。（JavaとかでのNullに相当）
``Nothing``かどうか検査するには、``If p Is Nothing Then ...``のように``Is``を使います。

##関連
クラスの応用編はこちら→https://qiita.com/Kamo123/items/a39c8f3ffdc5e665de98

##参考文献
VBA Developer's Handbook: Edition 2
https://www.google.co.jp/search?q=VBA+Developer's+Handbook%3A+Edition+2&ie=&oe=

VBAについて網羅的に載っている本です。
現在は絶版になっているようですが、google playストアで電子版を買えました。