和風スパゲティのレシピ

日本語でコーディングするExcelVBA

コンストラクタ(Initialize)に引数を渡せない問題

クラスモジュールに関する小ネタです。


VBAへのをこきおろす意見のなかでよく目にするのが、
「コンストラクタに引数を設定できなくて困る」です。

これがどういう意味なのかを手短に説明しますね。

なにができないのか

例えば、「Classサンプルクラス」というクラスを、

Public 処理シート As Worksheet

~~~処理シートの情報を取得するプロパティと、処理シートへのメソッドなどが続く

という中身で作ったとします。


あたりまえですが、このクラスにとって「処理シート」は必須のプロパティです。

なので、

Dim clsサンプルクラス As New Classサンプルクラス
Set clsサンプルクラス.処理シート = Worksheets("集計表")

大体は、こんな風にNewとSetが2行続くことが多いでしょう。


ですがいちいちこれを書くのは面倒ですよね?


クラスにはNewしたときに自動実行されるイベントプロシージャ「Initialize」という便利なものがあります。

このInitializeプロシージャに処理シートのセットも書いてしまいましょう。


このようにInitializeプロシージャを書いておけば↓

Private Sub Class_Initialize(このクラスで使うメインシート  As Worksheet)
    Set 処理シート = このクラスで使うメインシート
End Sub


先ほどのコードを、

Dim clsサンプルクラス As New Classサンプルクラス(Worksheets("集計表"))

こんな風に書き換えることができます。


どんなオブジェクトを主として扱うクラスなのかが一目でわかりますし、
なにより1行にスッキリしていていいですね!




これがExcelVBAではできないということです(´・ω・`)


クラスをNewした際に自動実行される「Class_Initialize」には、
引数を設定することができません。


この「クラスをNewした際に自動実行されるメソッド」のことを、
プログラミング世界での共通用語で「コンストラクタ」と呼びます。


上の例をそのまま直訳すれば、
「VBAではコンストラクタに引数を渡すことができない」
ということになります。

できなくて何に困るのか

Dim clsサンプルクラス As New Classサンプルクラス
Set clsサンプルクラス.処理シート = Worksheets("集計表")

こう書くのが面倒なだけならまだいい(良くない)のですが、

2行目を書き忘れてこれ以降の処理を書いてしまったり、
この2行の間になにか別のコードを書いてしまうかもしれません。


今回はWorksheetオブジェクトですので、最悪書き忘れても、
おそらく子供達であるRangeオブジェクトを処理するプロパティやメソッドで、
「おい親がいねーぞ!」というエラーをくれます。


しかし、クラスやメソッドによっては、初期の設定がなくてもエラーが出ず、
結果的に警告の出ないバグが発生する恐れがあります。


こういうのを未然に防ぎたいときに、引数付きのコンストラクタが欲しいのですが、
それがなくて困るわけですね。

対処法(簡単なもの)

一番簡単な方法は、コンストラクタを普通のSubプロシージャで自作しておき↓

Sub Init処理シートをセットする(このクラスで使うメインシート  As Worksheet)
    Set 処理シート = このクラスで使うメインシート
End Sub

 
Newしたらこれを呼ぶという自分ルールを設けることです。

Dim clsサンプルクラス As New Classサンプルクラス
Call clsサンプルクラス.Init処理シートをセットする(Worksheets("集計表"))

 

必ず「Init」を接頭する(あるいはInit4文字だけの名前にする)ことにしておき、
久しぶりに使うクラスでは、ひとまず「インスタンス.i」まで打ってみて、
予測候補にInitがあるかを確認するような、原始的な方法です。


原始的で、システム上の防御網は一切ありませんが、
とりあえず簡単に作れますので、自分しか使わないマクロはこれでも十分機能します。


もうちょっと防御網を作るとすれば、

Private isInitが実行済み As Boolean

というプロパティを用意しておき、
 

If Not isInitが実行済み Then Call Err.Raise(1000, , "Initがまだやぞ!")

でプロパティやメソッドを守るという方法もあります。
(全部でやるのが面倒なら、せめて大事なメソッドだけでも)


また、Initプロシージャ自体の内部にも、

If isInitが実行済み Then Call Err.Raise(1000, , "Init2回目やぞ!")

という防御網を作ることもできます。


ちょっと複雑なクラス程度であれば、これでもそこそこ機能してくれると思います。

引き出しの1つとして、心の片隅にでも置いておいてください。

おまけ:上級者の対処法

この問題は、上級者になればなるほど困る問題のようで、
VBAのエリートプログラマたちが、英知を持ち寄って解決策を考えています。

「コンストラクタ 引数 vba」で検索すれば、
いろいろな方法を紹介したページが大量にヒットします。


強固なマクロを組みたいときは、その方々に教えを請うてください。(わたしはできません)


ただ、「そんな強固さはたぶん一生いらないな」と思っている方にとっても、
引数付きコンストラクタの作り方は、とても面白い読み物だと思います。


上級者のマクロを見てみたくても、まずは「それがどんなマクロのどんな目的のコードなのか」を考えるところから始まるので、そもそもそれが難しすぎて、読み始めるのも大変なんですよね。


でもこの引数付きコンストラクタは、目的が明確です。

Dim clsサンプルクラス As New Classサンプルクラス(Worksheets("集計表"))

最終的にやりたいのはただこれだけです。


ただこれだけのために、エリートたちが戦う様子を、
「大変やなあ(´∀`)」
という対岸の火事目線でいいので気軽に読んでみましょう。


初学レベルの機能だけを組み合わせて、
感動するような手法をとっていたり、

「こんなのあったのか!」という、見たこともない機能を知る、いいきっかけになるんじゃないかなと思います。


上級者ってどんなコード書くんだろう??


という興味に対して、「まず何のためのコード?」をすっ飛ばして読める「引数付きコンストラクタ」は、いい題材だなと思います。

暇なときにでものぞいてみてください。



ちなみに最近自分が読んだ中で、網羅的で何パターンもあって、どのレベルの人でも面白く読めそうだなと思ったのがこちらの記事でした。

www.excel-chunchun.com

興味があったら、是非読んでみてください。