VBAクラスオブジェクト宣言法比較:1行宣言 vs. 分割宣言のメリットとデメリット
宣言とインスタンス化
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