🔖

VBAクラスオブジェクト宣言法比較:1行宣言 vs. 分割宣言のメリットとデメリット

2025/02/11に公開

宣言とインスタンス化

VBAでクラスオブジェクトの宣言とインスタンス化は、どのように書いていますか?
「1行でサクッと済ませるべきか、宣言とインスタンスの生成は分けて書くべきか?」と迷ったことはありませんか?
そもそも、書き方の違いで挙動が変わるのか、と考える方もいるのではないでしょうか。

例えば、MyCustomArray というクラスが定義されているとします。

dim myCustomArray as MyCustomArra
set myCustomArray = new MyCustomArray

と、宣言とインスタンス化を分けて書く方法もあれば、

dim myCustomArray as new MyCustomArray

と1行で書く方法もあります。
この書き方の違いで、何が変わるのでしょうか。

主な違いと考慮点

自動インスタンス化と明示的インスタンス化

1. 宣言とインスタンス化を1行でまとめた場合

この場合は「遅延初期化(Lazy Initialization)」と呼ばれ、必要な時に自動的にインスタンスが生成されます。
宣言時にはインスタンスは生成されず、メソッドやプロパティに初めてアクセスした際にインスタンスが作成されます。

2. 宣言とインスタンス化を分けた場合

インスタンスの生成タイミングを明示的に管理できます。
オブジェクトの生成タイミングが明確なため、コードの可読性が向上し、エラー処理やデバッグがしやすくなります。
ただし、コードが長くなりがちで、簡単な処理の場合は冗長に感じるかもしれません。

パフォーマンスとリソース管理

1. 宣言とインスタンス化を1行でまとめた場合

オブジェクトを必要とする前に、メソッドやプロパティにアクセスした瞬間に自動生成が発生するため、不要なタイミングでオブジェクトが生成されるリスクがあります。
特に、大規模なプログラムやリソースの重いオブジェクトの場合、パフォーマンスに影響が出る可能性があります。

2. 宣言とインスタンス化を分けた場合

必要なタイミングでインスタンスを生成できるため、リソース管理の面でも有利なケースが多いです。

可読性と保守性

1. 宣言とインスタンス化を1行でまとめた場合

コード行数が少なくすっきりしますが、初心者やチーム開発の場合、どこでオブジェクトが生成されるのかを追うのが少し難しくなる可能性があります。

2. 宣言とインスタンス化を分けた場合

宣言とインスタンス化のプロセスが明示されるため、後からコードを読む際に意図がはっきりし、保守性が向上します。
特に、初期化に失敗したときのエラー処理も明確に記述できるため、トラブルシューティングが楽になります

サンプルコード

実際にサンプルを作って動作を見てみましょう。
ここでは、MyClass というクラスが定義されているとします。

1行で宣言&インスタンス化の場合

Option Explicit

Private Sub Class_Initialize()
    Debug.Print "【MyClass】のコンストラクタが実行されました"
End Sub

Private Sub Class_Terminate()
    Debug.Print "【MyClass】のディストラクタが呼び出されました"
End Sub

Public Sub Greet()
    Debug.Print "【MyClass】こんにちは!"
End Sub

次に、1行で宣言&インスタンス化してインスタンスを生成するテストプロシージャを作成します。

Option Explicit

Sub TestLazyInitantiation()
    ' 1行で宣言とインスタンス化(遅延初期化)
    
    Dim lazyObj As New MyClass
    
    Debug.Print "【Lazy】メソッドの呼び出し前"
    
    'このタイミングでlazyObj.Greetにアクセスすると、
    ' 自動的にClass_Initializeが呼ばれインスタンスが生成される
    
    lazyObj.Greet
    
    Debug.Print "【Lazy】メソッドの呼び出し後"
    
    'オブジェクトを明示的にNothingにする
    
    Set lazyObj = Nothing
    Debug.Print "【Lazy】オブジェクトをNothingに設定"
    
    ' 再度メソッドを呼び出すと、また自動的に新しいインスタンスが生成される
    lazyObj.Greet
    Debug.Print "【Lazy】再度呼出し後"
End Sub

このプロシージャを実行すると、以下のような出力が得られます。

【Lazy】メソッドの呼び出し前
【MyClass】のコンストラクタが実行されました
【MyClass】こんにちは!
【Lazy】メソッドの呼び出し後
【MyClass】のディストラクタが呼び出されました
【Lazy】オブジェクトをNothingに設定
【MyClass】のコンストラクタが実行されました
【MyClass】こんにちは!
【Lazy】再度呼出し後
【MyClass】のディストラクタが呼び出されました

  • Dim lazyObj As New MyClass と書くと、実際には lazyObj にメソッドやプロパティへアクセスした瞬間に自動でインスタンスが生成されます。
  • 一度 Set lazyObj = Nothing としても、次に lazyObj を使うとまた新しいインスタンスが生成されるため、意図せずオブジェクトが再生成される点に注意してください。

宣言とインスタンス化を分けた場合

Sub TestExplicitInstantiation()
    ' まずは宣言だけ(まだインスタンスは生成されない)
    Dim explicitObj As MyClass
    
    Debug.Print "【Explicit】オブジェクト生成前"
    ' 自分でインスタンスを生成する必要がある
    Set explicitObj = New MyClass
    Debug.Print "【Explicit】オブジェクト生成後"
    
    explicitObj.Greet
    Debug.Print "【Explicit】メソッド呼び出し後"
    
    ' オブジェクトを Nothing に設定
    Set explicitObj = Nothing
    Debug.Print "【Explicit】オブジェクトを Nothing に設定"
    
    ' インスタンスが存在しない状態でメソッドを呼び出すとエラーになる
    On Error Resume Next
    explicitObj.Greet
    If Err.Number <> 0 Then
        Debug.Print "【Explicit】エラー発生:オブジェクトは Nothing です!"
    End If
    On Error GoTo 0
End Sub

  • Dim explicitObj As MyClass のみの宣言ではインスタンスは生成されません。
  • 自分で Set explicitObj = New MyClass と記述しない限り、実際のオブジェクトは存在しないため、意図したタイミングで生成できます。
  • 一度 Set explicitObj = Nothing とすると、その後に explicitObj にアクセスしても自動生成されず、エラーとなるため、オブジェクトの状態管理が明確です。

ループと遅延初期化

ループの中でDim obj As New MyClassのような1行宣言パターンを使うと、思いがけない挙動になることがあります。ここで注意してほしいのは、VBAでは変数の宣言はそのプロシージャ全体に有効になるため、ループ内に書いたとしても変数自体は1回だけ宣言され、ループ全体で再利用されるという点です。

例として、MyProfileというクラスが定義されていて、ループの中で繰り返し生成される場合を考えてみます。

Option Explicit

Private pIndex As Long

Public Property Get Index() As Long
    Index = pIndex
End Property

Public Property Let Index(value As Long)
    pIndex = value
End Property

Private Sub Class_Initialize()
    Debug.Print "【MyProfile】のコンストラクタが呼ばれました"
End Sub

Private Sub Class_Terminate()
    Debug.Print "【MyProfile】のディストラクタが呼ばれました"
End Sub

Option Explicit


Sub TestLoopReuse()
    Dim ProfileCollection As New Collection
    
    Dim i As Integer
    
    
    For i = 1 To 3
        Dim obj As New MyProfile
        
        obj.Index = i
        ' 初回のループで初めて obj を使用する時にインスタンス生成される
        ProfileCollection.Add obj

        ' ここで set obj = Nothingとするべき
    Next i
    
    
    ' プロパティを出力
    For i = 1 To ProfileCollection.Count
        Debug.Print ProfileCollection(i).Index
    Next i
End Sub

実行結果は以下の通りです。

【MyProfile】のコンストラクタが呼ばれました
 3 
 3 
 3 
【MyProfile】のディストラクタが呼ばれました

コードを書いた開発者としては
1 2 3と順に出力されるのを期待していたかもしれませんが
3 3 3と出力されています。
明示的にNothingが入力されない限りはインスタンスが再利用されるので
このような結果になるのです。

どちらの挙動を望むかは、プログラムの設計や目的に応じて選んでください。
ちょっとした違いが後々のバグや予期せぬ動作につながることもありますので、用途に合わせた正しい管理を心がけましょう

Discussion