※本ブログのページには広告主との提携による広告や宣伝、プロモーションが含まれます。当ブログを経由しての商品の購入や、サービス申し込みが発生すると、それらの提携企業からの成果報酬を受けとる場合があります。

エクセルVBA|グローバル変数やモジュール変数のスコープ(適用範囲)と宣言方法や有効期限について

Top image of Excel VBA variable scope (applicable range)

コンテンツ

はじめに

エクセルVBAでマクロの作成をしていると、変数をつかうことが必須となってきます。マクロの作成において、変数を自在に扱えるようになればマクロでできることが大幅に広がってきます。むしろ実務的なレベルでの自動化などは変数を利用することが必須であると言えます。

この記事では、エクセルVBAの変数が扱えるようになった人向けに、変数のスコープ(有効範囲)と有効期限(代入された値やオブジェクトが保持される期間)について解説しています。

まず、変数のスコープ(有効範囲)とは、ひとことで言えばその変数がどの範囲で利用できるかを指定したものとなります。マクロの処理をそれぞれでパート(部品)化する上で知っておきたい内容であり、大規模で複雑なマクロであればあるほど、ひとつのプロシージャに大量のコードを書きこむ方法が絶対にダメであるとは言えませんが、それぞれの役割をもった処理がまとめて書かれていると、コード量も多くなるだけでなく、問題発生時の原因の抽出が難しくなることや、プログラム全体を把握しづらいものとなります。

そこで、それぞれの処理ごとにプロシージャや、モジュールの単位でプログラムをまとめていくことが大切であり、可読性を良くすることや、メンテナンスのしやすくすることは、マクロ全体の質の向上につながります。
また、このようなマクロを構築していく上では、それぞれのモジュールやプロシージャで共有したい値やデータがでてきます。例えば、あるワークシートを変数に代入する処理を何度も繰り返し書く必要はありません。目的のシートを共有できる変数に代入しておけばほかのモジュールやプロシージャからでもあつかうことができます。

一度、ワークシートを代入した変数を、ほかのモジュールであつかう必要も出てくるため、宣言した変数のスコープ(適用範囲)を意識する必要があります。スコープ(適用範囲)を指定して値やデータを共有することは、便利さと引きかえにマクロの実行エラーや、意図しない動作の発生を招くリスクも含まれるため、これらの事象を予防するためにも、それぞれの変数がもつスコープ(適用範囲)を理解したうえで活用することが求められます。

変数のスコープ(適用範囲)は大きく3つに分類されます。他のモジュールからもあつかえるスコープ(適用範囲)の変数をグローバル変数といい、これがもっともスコープ(適用範囲)が広いものです。次に同じモジュールの他のプロシージャからもあつかえるスコープ(適用範囲)のモジュールレベルの変数があり、さいごにプロシージャのなかだけでのみ利用する変数があり、こちらがもっともスコープ(適用範囲)が狭いものです。

この記事では、VBAの変数がもつスコープ(適用範囲)の指定方法と指定したときの動作について、または変数や定数の有効期限について紹介していきます。これらの内容について記事を読んだ人の理解が少しでも深まれば幸いです。

エクセルVBAを基本から体系的に理解していきたいなら、おすすめの一冊。

この記事の内容

ここでは、エクセルVBAであつかう変数のスコープ(適用範囲)について解説しています。エクセルVBAの変数のスコープや有効期限について以下の内容について知ることができます。

  • エクセルVBAの変数のスコープ(適用範囲)とは
  • 変数のスコープ(適用範囲)のレベルについて
  • 定数におけるスコープ(適用範囲)について
  • 変数の有効期限とは
  • プロシージャレベル変数の値を保持するStaticステートメントとは
  • 配列変数のスコープ(適用範囲)
  • ユーザー定義型(構造体)のスコープ(適用範囲)
  • クラス変数のスコープ(適用範囲)
独学だと中々スキルが身についた実感が湧かない。学習環境を見直してみませんか?

エクセルで繰り返しや転記作業で苦しい思いをした経験はありませんか?
今まで苦労してきたその作業を簡単なプログラムをおぼえるだけで解決できる可能性があります。
なるべくお金や時間をかけずにエクセルマクロVBAを習得したい人にはこちらの「1日速習講座」がおすすめです。

VBAの変数におけるスコープ(適用範囲)

まずは、それぞれの変数のスコープ(適用範囲)について紹介します。

プロシージャレベル変数のスコープ(適用範囲)

プロシージャの中で宣言する変数です。

Subや、FunctionからEnd SubやEnd Functionのなかで、「Dim」ステートメントをつけて宣言しているものがこちらに当たります。こちらの変数は、プロシージャレベルの変数と呼ばれ、変数を宣言したプロシージャ以外からの値の代入や、参照はできません。

なお、プロシージャレベルの変数は、プロシージャの処理が完了した時点で変数の値はリセット(初期化)されます。

プロシージャレベルの変数をあつかったサンプルマクロ

標準モジュール
Option Explicit

Sub プロシージャレベルの変数() '①
    
    'プロシージャレベル変数"P"を宣言して100を代入する
    Dim P As Long: P = 100
    Debug.Print "税込価格: "; P * 1.1 & " 円です。"

End Sub
Sub プロシージャレベルの変数にアクセス() '②
    
    Debug.Print "税込価格: "; P * 1.1 & " 円です。"

End Sub

こちらのサンプルマクロでは、プロシージャを2つ作成しており「プロシージャレベルの変数()」を①、「プロシージャレベルの変数にアクセス()」を②としています。

①では、プロシージャレベル変数として、変数Pを宣言しています。また、①、②のいずれも、Debug.Printで変数Pの値を出力するコードを書いていますので実行してみます。

実行結果

①「プロシージャレベルの変数()」を実行すると以下がイミディエイトウィンドウに表示されます。

税込価格: 110 円です。

②「プロシージャレベルの変数にアクセス()」を実行すると「変数が定義されていない」エラーが発生します。

変数の宣言がされていないエラーメッセージの画像

この結果から、変数Pを宣言していないプロシージャの②から、変数Pの値を参照することができないことがわかります。

モジュールレベル変数のスコープ(適用範囲)

プロシージャレベルの変数と異なり、モジュールの宣言セクションで宣言をする変数です。

宣言セクションで、Dimステートメントや、Privateステートメントをつかって宣言することでモジュールレベルの変数となります。

プロシージャレベルの変数と異なる点として、同じモジュールに書かれた他のプロシージャからでも値の代入や参照ができます。なお、モジュールレベルの変数の有効期限は、宣言したモジュールの処理が完了するまでです。

モジュールレベルの変数をあつかったサンプルマクロ

標準モジュール
Option Explicit

Dim M1 As Long 'Dimステートメントで宣言
Private M2 As Long 'Privateステートメントで宣言

Sub モジュールレベルの変数A() '①

    M1 = 1000 'Dimで宣言した変数に値を代入する
    M2 = 10000 'Privateで宣言した変数に値を代入する
    
    Debug.Print "税込価格: "; M1 * 1.1 & " 円です。"
    Debug.Print "税込価格: "; M2 * 1.1 & " 円です。"
    
    Call モジュールレベルの変数B '他プロシージャ②を呼び出す

End Sub
Sub モジュールレベルの変数B() '②
    
    M1 = M1 / 2 'Dimで宣言した変数に値を更新する
    M2 = M2 / 2 'Privateで宣言した変数に値を更新する

    Debug.Print "税込価格: "; M1 * 1.1 & " 円です。"
    Debug.Print "税込価格: "; M2 * 1.1 & " 円です。"

End Sub

こちらのサンプルマクロでは、標準モジュールの宣言セクションで「M1」と「M2」という変数をDimステートメントと、Privateステートメントをつかって宣言しています。サンプルマクロの①を実行すると「M1」には1000が、「M2」には10000が代入されます。そして、それぞれの変数の値に1.1を掛けた数値をイミディエイトウィンドウに出力しています。

その後、Callステートメントをつかって②を呼び出して、変数「M1」と「M2」の値を2で割った数値に更新しています。イミディエイトウィンドウに表示された以下の出力結果から、それぞれの変数の値が出力できていることが確認できます。

なお、ここでのポイントですが、Callステートメントで②を呼び出すときに変数「M1」と「M2」を引数として指定していなくても変数に代入された値が保持できている点です。モジュールレベルの変数は、変数を宣言したモジュールの処理が完了するまでの間は値が保持されるため、②のプロシージャに処理が移っても変数の値は引き継がれています。
なお、変数の値がリセット(初期化)されるタイミングは、プロシージャ①が完了したタイミングです。

プロシージャ①から②を呼び出し、②が終了のしたあとで①が終了する順番になるね。

実行結果

税込価格: 1100 円です。
税込価格: 11000 円です。
税込価格: 550 円です。
税込価格: 5500 円です。

パブリックレベル変数のスコープ(適用範囲)

プロシージャレベルの変数と異なり、モジュールの宣言セクションで宣言をする変数です。宣言セクションで、Publicステートメントをつかって宣言することでパブリックレベルの変数となります。

Globalステートメントをつかっても同じ宣言が可能ですが、これは、VB(ビジュアルベーシック​)の古いバージョンとの互換性のためにつかわれていたものが残っているためなので、VBAではPublicステートメントをつかうことが一般的です。また、Globalステートメントは標準モジュール以外に書きこむとエラーなり宣言はできません。

パブリックレベルの変数は、グローバル変数とも呼ばれ、対象となる変数を宣言したモジュール外からの値の代入や参照が可能です。なお、パブリックレベルの変数の有効期限はブック(ファイル)を閉じるまでです。

パブリックレベルの変数をあつかったサンプルマクロ

パブリックレベルの変数をあつかうマクロですが、ここでは標準モジュールを2つ準備します。「標準モジュール(変数のスコープ2_1)」の宣言セクションで宣言したパブリックレベルの変数に、値の代入や出力を実行してみます。

以下に、それぞれのモジュールに書いたプロシージャと動作について解説します。

標準モジュール(変数のスコープ2_1)
Option Explicit

Public G As String 'パブリックレベル変数を宣言

Sub パブリックレベルの変数A() '①

    G = G &"株式会社オブジェクト"   'Publicで宣言した変数に値を代入する
    Debug.Print Mid(G, InStr(G, "社") + 1) '"社"の文字よりも右側だけ抽出する
    Call パブリックレベルの変数B '他プロシージャ②を呼び出す

End Sub
Sub パブリックレベルの変数B() '②

    G = G & " Plus One"  'Publicで宣言した変数に値を代入する
    Debug.Print Mid(G, InStr(G, "社") + 1) '"社"の文字よりも右側だけ抽出する
    Call 変数のスコープ2_2.パブリックレベルの変数C '他モジュールのプロシージャ③を呼び出す

End Sub

上記のモジュールには、宣言セクションでPublicステートメントをつけて変数「G」を宣言しています。なお、今回はデータ型を文字列(String)型にしていますので、文字列を代入・参照していくマクロとなります。

また、こちらのモジュールは、2つのプロシージャを作成しています。コメントでそれぞれに①と②という番号づけしていますので、以降の説明にはこちらの番号を利用したいと思います。

まず、①のプロシージャを実行すると、変数「G」に「株式会社オブジェクト」も文字列を代入します。その直ぐ下の行において、変数「G」にイミディエイトウィンドウに出力する処理を行います。なお、ここでは社名である”オブジェクト”の部分のみを取り出す文字列操作処理を想定しており、Mid関数や、InStr関数をつかって出力する内容を操作しています。

続いて、Callステートメントをつかって、②のプロシージャを呼び出していますが、変数「G」を引数には指定していません。プロシージャ②のなかで、変数「G」に先ほど①のプロシージャで代入された値を更新し、①と同様にイミディエイトウィンドウに出力します。

さらに、Callステートメントをつかって、他のモジュールに書かれたプロシージャ(パブリックレベルの変数C)を呼び出す処理をします。

標準モジュール(変数のスコープ2_2)
Option Explicit

Sub パブリックレベルの変数C() '③

    G = G & " ぷらす 2"  'Publicで宣言した変数に値を代入する
    Debug.Print Mid(G, InStr(G, "社") + 1) '"社"の文字よりも右側だけ抽出する

End Sub

 パブリックレベルの変数Bによって呼び出されたこちらのモジュールのプロシージャ③ですが、②と同じく変数「G」に代入された値を更新し、①と同様にイミディエイトウィンドウに出力しています。

それでは、実行結果を見てみましょう。

実行結果

1回目の実行結果

オブジェクト
オブジェクト Plus One
オブジェクト Plus One ぷらす 2

2回目の実行結果

オブジェクト Plus One ぷらす 2株式会社オブジェクト
オブジェクト Plus One ぷらす 2株式会社オブジェクト Plus One
オブジェクト Plus One ぷらす 2株式会社オブジェクト Plus One ぷらす 2

上の実行結果は、マクロを2回実行したものです。上が3行がプロシージャ①、②、③の1回目の実行結果で、4行目から6行目は、2回目の実行結果になります。

標準モジュール(変数のスコープ2_1)の宣言セクションで、publicステートメントをつかって宣言した変数「G」には、プロシージャ①の中で「株式会社オブジェクト」が代入されます。その後、プロシージャ②において「株式会社オブジェクト」に「Plus One」という文字列を付け加えています。さらにプロシージャ③では、「オブジェクト Plus One」に「ぷらす 2」の文字列を付け加える処理をしています。

この結果から、変数「G」に代入された値(文字列)は、プロシージャやモジュールが他のものに移ったあともリセット(初期化)されずに保持されていることがわかります。なお、2回目の実行結果からもわかるように、変数「G」に代入された値はマクロの処理が完了しても保持されており、実行を繰り返す度に文字列を付け加えていくことになります。

この結果から、パブリックレベルの変数の有効期限が確認できました。変数「G”」の値は、ブック(ファイル)を閉じることでリセット(初期化)されます。

このように、Publicステートメントで宣言した変数は、他のモジュールからも値の代入や出力が可能となりますので、プロジェクト全体で共通する変数などが必要なときに有効です。

パブリック・モジュールレベル変数をあつかう時の注意点

これまでの説明で、パブリックレベルの変数とモジュールレベルの変数は、他のモジュールやプロシージャからもあつかうことができることを説明しました。

ここでは、プロシージャレベルよりもスコープ(適用範囲)が広い、パブリックレベルやモジュールレベルの変数をあつかう際の注意点について紹介します。

変数のスコープ(適用範囲)を広げることのリスクについて紹介するよ。

パブリック・モジュールレベル変数名の重複

宣言セクションで宣言をするパブリック・モジュール変数を利用する場合は、変数名の重複に注意が必要です。宣言セクションで宣言した変数名と同じ変数を宣言した場合は、以下の画像のように「同じ適用範囲内で宣言が重複しています。」とエラーメッセージを表示し、コンパイルエラーが発生します。

変数の宣言が重複している場合に表示されるエラーメッセージの画像

パブリック・モジュールレベル変数名のつけかた

宣言セクションで宣言をするパブリック・モジュール変数を利用する場合は、マクロの処理が、プロシージャやモジュール間を越えたものになることが多いため、変数名からその変数がどのようなデータをあつかっているかを想像しやすいものにしておくことが望ましいです。

理由としては、スコープ(適用範囲)が広い変数は、プロシージャレベルの変数とちがい、変数を宣言しているプロシージャや、モジュールから離れたプロシージャで処理を実行することがあるため、可読性の低下が見込まれるためです。

他のプロシージャからパブリック・モジュールレベル変数を更新する

パブリックやモジュールレベルの変数は、他のプロシージャやモジュールからの影響をうける可能性があります。これは、スコープ(適用範囲)が広い変数のメリットの裏返しであり、他のプロシージャやモジュールから、値を操作(追加・変更・削除など)が出来てしまうことでマクロ制作者の意図しない値に書き換えられてしまうリスクとなります。

例えば、For NextやDo Loopなどの繰り返し処理の書きかたを紹介した記事でも触れたように、変数をつかって繰り返す回数を制御しているときに、他のプロシージャによってその変数が書き換えられたことで無限ループが発生する可能性があります。

他にも、データ型が異なる値を代入しようとして型が一致しないエラーなど、スコープ(適用範囲)が広い変数をあつかうことのリスクには注意が必要です。

「For Next」や「Do Loop」を使った繰り返し処理の書きかたは、過去の記事であつかっています。
以下のリンクより読むことができますので合わせてご覧ください。

上記のリンクから参考記事を読むことができます。

パブリックレベルの変数は宣言したモジュールをわかるようにする

パブリックレベルの変数は、他のモジュール内のプロシージャによる影響をうけてしまう恐れがあるため、エラーが発生した場合は処理をさかのぼって、どこが原因でエラーになる処理が実行されているかを検証する必要がでてきます。

この作業を容易でスムーズにするためにも、パブリック変数に値を代入する、出力するなどの処理を実行する場合に、その変数を宣言したモジュールがわかるようにコードを書くことや、コメントに残すことが望ましいと言えます。

具体的には、以下のマクロにおける下線部分のようにコメントアウトをすることや、コード上でモジュール名を省略することが可能な場合であっても、敢えて書き残すことでその変数がどのモジュールで宣言されたのかが、ひと目でわかるようにする方法があります。

Sモジュール1
Option Explicit

Public Sモジュール1で宣言 As Date
Sモジュール2
Option Explicit

Sub Sモジュール2のプロシージャ()
    
    '①パブリックレベルの変数を宣言したモジュールがわかるようにコメントする
    Sモジュール1で宣言 = Date 'TESTモジュール1で宣言した変数に今日の日付を代入する
    Debug.Print Sモジュール1で宣言
    
    '②どのモジュールで宣言したかをコードにも書き残す
    Sモジュール1.Sモジュール1で宣言 = DateAdd("d", 1, Date) '宣言したモジュール(Sモジュール1)を明記する
    Debug.Print Sモジュール1.Sモジュール1で宣言
    
End Sub

補足として、②のようにVBAのコードにモジュール(オブジェクト)名まで書くと、コードが長くなる可能性はあります。
ただ、コード量をなるべく減らし、スッキリさせて可読性を上げる行為は、マクロ全体を把握しやすくし、エラーの発生を予防する、機能拡大などのアップデートを容易にするなどの目的のための手段です。このケースの場合などで、モジュール名を敢えて書き残すことで「プログラムを見やすくする、理解を促す」という同じ目的に従ったものであれば誤りではなく、手段の選択だと言えるはずです。

コード量を減らすことが目的ではないということだね。

パブリックレベルの変数は使用しているプロシージャをわかるようにする

前項と同じく、パブリックレベルの変数をあつかって処理をしているプロシージャがわかるようにコメントを残すことで、意図しない動作の予防や、エラー発生時の検証作業、機能の拡大などのアップデートをしやすくする効果があるため、パブリックレベルの変数をあつかうプロシージャがわかるようにコメントを残すことも望ましいと言えます。

コメントの書きかたは自由ですが、わかりやすくするためにサンプルマクロでは、下線部のようにモジュール名(プロシージャ名)で利用とコメントアウトしています。

Sモジュール1
Option Explicit

'Sモジュール2(Sモジュール2のプロシージャ)で利用
Public Sモジュール1で宣言 As Date
Sモジュール2
Option Explicit

Sub Sモジュール2のプロシージャ()
    
    '①パブリックレベルの変数を宣言したモジュールがわかるようにコメントする
    Sモジュール1で宣言 = Date 'TESTモジュール1で宣言した変数に今日の日付を代入する
    Debug.Print Sモジュール1で宣言
    
    '②どのモジュールで宣言したかをコードにも書き残す
    Sモジュール1.Sモジュール1で宣言 = DateAdd("d", 1, Date) '宣言したモジュール(Sモジュール1)を明記する
    Debug.Print Sモジュール1.Sモジュール1で宣言
    
End Sub
PCでスキルアップをしたい・Excelをしっかり学んで社内の評価を高めたい人は必見!
実務をプロから学べる「ユースフル」は講座の動画は永年見放題。安心のQ&A機能で分からないを解決。

VBAの定数(Const)におけるスコープ(適用範囲)

これまでは変数のスコープ(適用範囲)について解説してきました。

エクセルVBAには、変数と同じく定数と呼ばれるものがあります。定数は変数と同じように宣言し、値を代入することで利用が可能であり、宣言の方法によって、スコープ(適用範囲)を指定することができます。

まずは、エクセルVBAにおける定数について簡単に説明します。

エクセルVBAにおける定数とは

定数は、変数と同じように宣言し、値を代入・出力することができます。宣言の方法はプロシージャまたは、モジュールの宣言セクションでおこないます。このとき、「Const」ステートメントを用いることで定数の宣言ができます。

定数は、変数のように多くの種類の値を代入することはできません。宣言できるデータ型は、Byte、Boolean、Integer、Long、Currency、Single、Double、Date、String、Variant型のいずれかになりますので、これらに当てはまる値を代入することができます。

変数であつかえるデータ型については、以下の記事でも紹介していますので合わせてご覧ください。

上記のリンクから参考記事を読むことができます。

また、定数は宣言のタイミングで値を代入する必要があります。さらに、一度代入をしたあとでは値を更新することはできません。そのため定数は、宣言時に定めた値を利用する目的で使います。

定数の特徴について
  • 定数の宣言はConstステートメントをつかう
  • 定数は宣言と同時に値を代入する必要がある
  • 定数は初回の代入以降で値の更新はできない

エクセルVBAにおける定数宣言時の構文

●定数宣言の構文

[ Public | Private ] Const 定数名 as データ型 = 代入する値

※「Public」や「Private」ステートメントは、宣言セクションのみで使用可能です。
※宣言セクションでこれらのステートメントを省略した場合は「Private」になります。
※パブリックレベルの定数は、標準モジュールでのみ宣言可能です。ブックやシート、フォーム、クラスモジュールでは「Public」での宣言はできません。

エクセルVBAの定数の使いどころ

定数は、余程のことがない限り変わらない値をあつかう場合に利用すると良いでしょう。
以下に定数をあつかったサンプルマクロを作りましたので、参考にしてください。

定数をつかったサンプルマクロ

定数をつかったサンプルマクロで処理する表

マクロの動作を分かりやすくするために、簡単な表(なんとかひょう)を作成し、薄い色のついたセルの部分を算出した結果を出力してみます。

標準モジュール(定数サンプル)
Option Explicit

Sub 計算するプロシージャ()
    
    Const Duty As Double = 0.1 '税率用定数の宣言と代入をする
    Dim i As Long: i = 5 '繰り返し用変数の宣言と代入をする
    
    'Withでコードをまとめる
    With ThisWorkbook.Sheets("Const使用例")
    
        Do Until .Cells(i, 2) = ""
            .Cells(i, 6) = Format(.Cells(i, 5) * Duty, "#,##0") '各行に税額を出力
            .Cells(i, 7) = Format(.Cells(i, 5) + .Cells(i, 5) * Duty, "#,##0") '各行に税込価格を出力
            i = i + 1
        Loop
        
        '合計行まで行を行数を送る処理
        Do While .Cells(i, 2) = ""
            i = i + 1
        Loop
        
        '合計行の販売価格・税額・税込価格の合計値を出力
        .Cells(i, 5) = Application.WorksheetFunction.Sum(.Range(.Cells(5, 5), .Cells(i - 1, 5))) '販売価格合計
        .Cells(i, 6) = Application.WorksheetFunction.Sum(.Range(.Cells(5, 6), .Cells(i - 1, 6))) '税額合計
        .Cells(i, 7) = Application.WorksheetFunction.Sum(.Range(.Cells(5, 7), .Cells(i - 1, 7))) '税込販売価格合計
    
    End With
    
End Sub

こちらのマクロでは、定数はConstステートメントをつかって「Duty」という名前で宣言しています。また、データの型はDouble(小数点)型とし、宣言と同時に0.1を代入しています。

このように定数で指定しておくことで、たとえ将来的に税率が変わったとしても、定数に代入する値だけを更新すればマクロのアップデートができるため、複雑なコードが書きこまれた部分を触ることがないといったメリットがあります。

それでは、マクロを実行したあとの表の画像を見ていきます。

実行結果

定数をつかったサンプルマクロで処理する表(実行結果)

各行の販売価格(税込)と販売価格、税額、販売価格(税込)の合計値を出力しました。

税率の部分は変数でも良いのでは?と思われるかもしれませんが、もちろん変数でも問題なく動作します。また、変数をつかった同じようなコードであった場合のアップデートにかかる手間も全く変わらないですが、定数をつかうことの利点は”一度代入した値は更新できない”ことであり、他のプロシージャからの処理の影響をうけないところだと言えます。この点がマクロに定数をつかう理由として、変数と区別ができる最大のポイントだと言えます。

さて、前置きが長くなってしまいましたが、次項からは定数のスコープについて解説していきます。

定数のスコープ(適用範囲)の種類

それでは、定数のスコープ(適用範囲)の種類ですが、変数と同様に以下のとおりとなります。

サンプルマクロとともに、それぞれを詳しくみていきましょう。

プロシージャレベルのスコープ(適用範囲)の定数

プロシージャ内で宣言したものは、こちらになります。
プロシージャ内でのみ定数の値の利用が可能であり、他のプロシージャやモジュールからは値の利用ができません。

プロシージャレベルのスコープ(適用範囲)の定数をあつかったサンプルマクロ

標準モジュール(定数のスコープ1_1)
Option Explicit

Sub プロシージャレベルの定数() '①
    
    'プロシージャレベル定数"PC"を宣言して0.1を代入する
    '※定数名のPCは、procedureの「P」とConstの「C」の頭文字です。
    Const PC As Double = 0.1
  
  'インプットボックスで100が入力されたと想定
    Dim Price As Long: Price = Application.InputBox("価格を入力してください。", "価格入力")
    Debug.Print "税込価格: \"; Price + Price * PC; " 円です。"

End Sub
Sub プロシージャレベルの定数にアクセス() '②※定数PCが定義されていないため、変数が定義されていないエラーとなる
    
  'インプットボックスで100が入力されたと想定
    Dim Price As Long: Price = Application.InputBox("価格を入力してください。", "価格入力")

    'プロシージャレベル定数"PC"の値を利用する
    Debug.Print "税込価格: \"; Price + Price * PC; " 円です。"

End Sub

プロシージャ①を実行すると、価格を入力するインプットボックスを表示します。ここでは、100 を入力した結果を確認してみます。次にプロシージャ②ですが、こちらはプロシージャレベルの定数”PC”を異なるプロシージャから利用しようとしているため、「変数が定義されていない」エラーが発生します。

実行結果

プロシージャ①の実行結果

税込価格: ¥ 110 円です。

プロシージャ②の実行結果

定数の宣言がされていないエラーメッセージの画像

定数名「PC」は、プロシージャレベルの定数であるため、宣言したプロシージャ①では利用が可能であり、他のプロシージャである②からでは、値の利用ができないことが確認できていることから、定数「PC」のスコープ(適用範囲)は宣言したとおり、宣言したプロシージャのみであることが確認できました。

モジュールレベルのスコープ(適用範囲)の定数

宣言したモジュール以外から定数の値が利用できます。各モジュールの宣言セクションにて「Private」ステートメントをつけることで宣言できます。また、「Public」や「Private」ステートメントを省略した場合は、モジュールレベル(Privateで宣言したものと同様)の定数となります。

モジュールレベルのスコープ(適用範囲)の定数をあつかったサンプルマクロ

標準モジュール(定数のスコープ1_2)
Option Explicit

'定数名のMCは、moduleの「M」とConstの「C」の頭文字です。
Private Const MC As String = "これはプライベート定数です。"

Sub パブリック_モジュールレベルの定数にアクセス正常() '①定数MCの値を出力する
    
    Debug.Print MC 'これはプライベート定数です。

End Sub
Sub モジュールレベルの定数にアクセス不正() '②※定数MCの値は更新できないためエラーとなる
    
    Debug.Print MC
    MC = "MCの文字列を書き換えました。" '定数には値を代入できませんのエラーメッセージを表示する

End Sub

プロシージャ①を実行すると、定数「MC」に代入された文字列”これはプライベート定数です。”を出力します。次にプロシージャ②では、プロシージャレベルの定数「MC」の値を更新しようとしていますが、こちらは「定数には値を代入できません」のエラーが発生します。

実行結果

プロシージャ①の実行結果

これはプライベート定数です。

プロシージャ②の実行結果

定数に値を代入しようとしたときのエラーメッセージの画像

定数名「MC」は、モジュールレベルの定数であるためスコープ(適用範囲)から言えば、プロシージャ①だけでなく、プロシージャ②からも利用が可能です。しかし、プロシージャ②では、定数「MC」に代入された値を更新するコードを書いているためにエラーが発生する結果となっています。

なお、プロシージャ②の定数「MC」の値を更新するコード部分である「MC = “MCの文字列を書き換えました。” 」をコメントアウトすると、実行時エラーは発生せずにプロシージャ①と全く同じ結果となります。

パブリックレベルのスコープ(適用範囲)の定数

宣言したモジュール以外から定数の値が利用できます。標準モジュールの宣言セクションにて「Public」ステートメントをつけることで宣言できます。ただし、パブリックレベルの定数は標準モジュール以外では宣言できません。

パブリックレベルのスコープ(適用範囲)の定数をあつかったサンプルマクロ

標準モジュール(定数のスコープ1_3)
Option Explicit

'定数名のGCは、globalの「M」とConstの「C」の頭文字です。
Public Const GC As String = "これはパブリック定数です。"

Sub パブリック_モジュールレベルの定数にアクセス正常() '①定数GCの値を出力する
    
    Debug.Print GC 'これはパブリック定数です。

End Sub
Sub パブリックレベルの定数にアクセス不正() '②※定数GCの値は更新できないためエラーとなる
    
    Debug.Print GC
    GC = "GCの文字列を書き換えました。" '定数には値を代入できませんのエラーメッセージを表示する

End Sub
標準モジュール2(定数のスコープ1_4)
Option Explicit

Sub パブリックレベルの定数にアクセス正常() '③定数GCの値を出力する
    
    Debug.Print GC 'これはパブリック定数です。

End Sub

定数「GC」はパブリックレベルの定数であるため、他のモジュールからでも値を利用できます。つまりプロシージャ①と③については、宣言時に代入した文字列である”これはパブリック定数です。”が表示できます。

さらに、定数「GC」のスコープ(適用範囲)から言えば、宣言元であるモジュールに含まれるプロシージャ②でも利用ができるはずですが、プロシージャ②には定数「GC」の値を更新するコードを書いているため、実行時エラーが発生します。

実行結果

プロシージャ①と③の実行結果

これはパブリック定数です。

プロシージャ②の実行結果

定数に値を代入しようとしたときのエラーメッセージの画像

定数「GC」はパブリックレベルのスコープ(適用範囲)であるため、他のモジュール(標準モジュール2)にあるプロシージャ③から利用できていることがわかります。なお、パブリックレベルの定数については、標準モジュール以外で宣言しようとすると、VBEで構文エラーのようにあつかわれるため注意が必要です。

定数のスコープ(適用範囲)の種類と注意点まとめ

ここまでで、エクセルVBAの定数におけるスコープ(適用範囲)についての解説と、サンプルマクロを通して実際の動作を確認してきました。以下の表に定数のスコープ(適用範囲)の特徴と注意点をまとめましたので参考にしてください。

宣言場所 ステートメント 値の代入時期 値の更新 宣言できるデータ型
宣言セクション
※標準モジュールのみ宣言可能
Public 宣言と同時 不可 Byte、Boolean、Integer、Long、Currency、Single、Double、Date、String、Variant型に限る
宣言セクション Private
プロシージャの中 Const
※宣言セクションで宣言した定数のステートメントを省略した場合、Privateが適用されます。

定数は変わらない値を代入しておくものだね。

独学の学習効率でお悩みの人必見!
<動画学習見放題サービス>

初心者にやさしいチューターなら今すぐにはじめられる

オンラインで学習したい
プログラミングで副業をはじめたい
パソコン・エクセルの学習をしたい

VBAの変数や定数の有効期限について

変数は宣言する方法によって使用できる期間があり、これを変数の有効期限と言います。

変数の有効期限は、変数の宣言方法によって異なります。プロシージャのなかで宣言した変数(プロシージャレベル変数)は、宣言したプロシージャの処理が完了した時点で初期化します。プロシージャレベル変数のスコープ(適用範囲)のまま、プロシージャの処理が完了しても変数の値を保持したいときには、「Static」ステートメントを使います。

また、標準モジュールの宣言セクションで宣言したモジュール変数のうち、「Dim」や「Private」で宣言したものは、そのモジュールの処理が完了するまでが変数の有効期限となり、「Public」で宣言したものはブック(ファイル)を閉じるまで値が保持される有効期限となります。

標準モジュールで宣言した変数の有効期限をまとめると以下のとおりです。

モジュール種別 宣言する場所 ステートメント スコープ(適用範囲) 有効期限
標準モジュール プロシージャの中 Dim 宣言プロシージャ内 プロシージャの処理完了まで
Static ブック(ファイル)を閉じるまで
宣言セクション Dim 宣言モジュール内 モジュールの処理完了まで
Private
Public すべてのモジュール内 ブック(ファイル)を閉じるまで
Staticステートメントはプロシージャ内でのみ使用可能であり、Private、Publicステートメントは宣言セクションのみで使用可能なステートメントです。

変数の有効期限の一覧は動作を確認した上で記載しています。ただし、標準モジュールの変数の保持がどういった動作により破棄されるのかは、本記事を書くうえで参考にした資料上でも予想の範囲とされています。そのため、マクロの安全な運用を考えるのであれば、変数の有効期限を完全に信用することはせずにワークシートなどの信頼性の高い形で保存することが勧められています。

また、上記の表は標準モジュールで宣言したときの変数の有効期限です。標準モジュール以外のモジュールでは動作が異なるものが存在します。例えばクラスモジュールで「Public」をつかった変数の有効期限は、そのモジュールの処理が完了するまでとなっており、標準モジュールで宣言した場合と異なった結果となっています。

Staticステートメントでプロシージャレベル変数の有効期限を拡張する

Staticステートメントで宣言した変数は、宣言したプロシージャのマクロが完了しても、代入した変数の値が保持されるようになります。

Staticステートメントの使いかた

Staticステートメントは、プロシージャ内でのみ利用が可能です。プロシージャのなかで以下の構文に従って宣言します。

●Staticステートメント構文

Static 変数名  As データ型

Dimステートメントの部分をStaticに変えるだけだよ。

Staticステートメントで宣言した変数をつかったサンプルマクロ

Staticステートメントで各データ型の変数を宣言すると以下のとおりです。Staticで宣言した変数に値を代入して、イミディエイトウィンドウに出力して値が保持されることを確認してみます。

標準モジュール
Option Explicit

Sub 変数のスコープ1()

'変数Sample1~6をStaticステートメントで宣言して扱うマクロ

'数値型
Static Sample1 As Integer
Static Sample2 As Long

'文字列型
Static Sample3 As String

'配列
Static Sample4(2) '静的配列
Static Sample5(1 To 3) '静的配列で下限から上限の添え字あり
Static Sample6() '動的配列
ReDim Preserve Sample6(2) '動的配列を再定義

'オブジェクト型
Static SampleSheet As Worksheet 'ワークシート
Static SampleRange As Range 'セル

'値を代入する
Sample1 = Sample1 + 1
Sample2 = Sample2 + 5
Sample3 = Sample3 & "文字列"

If SampleSheet Is Nothing Then 'SampleSheetが空なら
    Set SampleSheet = ThisWorkbook.Worksheets("Sheet1")
ElseIf SampleSheet.Name = "Sheet1" Then  'SampleSheetがSheet1なら
    Set SampleSheet = ThisWorkbook.Worksheets("Sheet2")
End If

If SampleRange Is Nothing Then 'SampleRangeが空なら
    Set SampleRange = ThisWorkbook.Worksheets("Sheet2").Range("B2")
ElseIf SampleRange.Address = "$B$2" Then 'SampleRangeがB2なら
    Set SampleRange = ThisWorkbook.Worksheets("Sheet2").Range("B3")
End If

Dim i As Integer '繰り返し用変数
For i = 1 To 3
    Sample4(i - 1) = Sample4(i - 1) + i
    
    If i = 1 Then 'iが1の場合
        Sample5(i) = Sample5(i) & "A"
    ElseIf i = 2 Then 'iが2の場合
        Sample5(i) = Sample5(i) & "B"
    ElseIf i = 3 Then 'iが3の場合
        Sample5(i) = Sample5(i) & "C"
    End If
    
    Select Case i
    Case 1'変数iが1のとき
        Sample6(i - 1) = Sample6(i - 1) & "あ"
    Case 2'変数iが2のとき
        Sample6(i - 1) = Sample6(i - 1) & "い"
    Case 3'変数iが3のとき
        Sample6(i - 1) = Sample6(i - 1) & "う"
    End Select

Next i

'変数に代入されているものを出力する
Debug.Print "Sample1は " & Sample1
Debug.Print "Sample2は " & Sample2
Debug.Print "Sample3は " & Sample3
Debug.Print "Sample4は " & Sample4(0) & ":" & Sample4(1) & ":" & Sample4(2)
Debug.Print "Sample5は " & Sample5(1) & ":" & Sample5(2) & ":" & Sample5(3)
Debug.Print "Sample6は " & Sample6(0) & ":" & Sample6(1) & ":" & Sample6(2)
Debug.Print "SampleSheetは " & SampleSheet.Name
Debug.Print "SampleRangeは " & SampleRange.Value

End Sub

上記のサンプルマクロは、Sample1~6SampleSheetSampleRangeという名前の変数を「Static」ステートメントをつけて宣言しています。パッと見たところコードの量は多いですが、中身のマクロの処理は単純なもので、それぞれの変数を宣言して値やオブジェクトを代入や参照し、出力するといったシンプルなものです。

変数(箱)を作って、中に値を入れてそれを出しているだけだよ。

それでは、サンプルマクロの実行結果をみてみましょう。

実行結果(1回目)

Sample1は 1
Sample2は 5
Sample3は 文字列
Sample4は 1:2:3
Sample5は A:B:C
Sample6は あ:い:う
SampleSheetは Sheet1
SampleRangeは  SampleRangeのセル

イミディエイトウィンドウを確認すると、それぞれに値やオブジェクトが代入されていることがわかります。それでは、このマクロをもう一度実行してみるとどうなるでしょうか。

実行結果(2回目)

Sample1は 2
Sample2は 10
Sample3は 文字列文字列
Sample4は 2:4:6
Sample5は AA:BB:CC
Sample6は ああ:いい:うう
SampleSheetは Sheet2
SampleRangeは  SampleRangeのセル2

2回目の実行により、1回目で変数に代入された値が保持されていることがわかります。それぞれの変数のうごきについては、以下のとおりになります。

Static宣言した変数の動作
  • Sample1
    1回目の実行で1が代入されており、2回目の実行で1が足されて2になる。
  • Sample2
    1回目の実行で5が代入されており、2回目の実行で5が足されて10になる。
  • Sample3
    1回目の実行で”文字列”が代入されており、2回目の実行で”文字列文字列”になる。
  • Sample4(要素数が3つの静的配列)
    • Sample4(0)は、1回目の実行で1が代入され、2回目の実行で1が足されて2になる。
    • Sample4(1)は、1回目の実行で2が代入され、2回目の実行で2が足されて4になる。
    • Sample4(2)は、1回目の実行で3が代入され、2回目の実行で3が足されて6になる。
  • Sample5(要素数が3つの静的配列)
    • Sample5(1)は、1回目の実行で”A”が代入され、2回目の実行で”A”が足されて”AA”になる。
    • Sample5(2)は、1回目の実行で”B”が代入され、2回目の実行で”B”が足されて”BB”になる。
    • Sample5(3)は、1回目の実行で”C”が代入され、2回目の実行で”C”が足されて”CC”になる。
  • Sample6(要素数が3つの動的配列)
    • Sample6(1)は、1回目の実行であが代入され、2回目の実行で”あ”が足されて”ああ”になる。
    • Sample6(2)は、1回目の実行でいが代入され、2回目の実行で”い”が足されて”いい”になる。
    • Sample6(3)は、1回目の実行でうが代入され、2回目の実行で”う”が足されて”うう”になる。
  • SampleSheet(ワークシート型のオブジェクト変数)
    1回目の実行でSheet1を参照していて、2回目の実行でSheet2に更新される
    ※SampleSheet Is Nothing Then の判定結果と、SampleSheet.Nameによって変数の値が保持されていることを確認しています。
  • SampleRange(レンジ型のオブジェクト変数)
    1回目の実行でSheet2のB2セルを参照していて、2回目の実行でSheet2のB3セルに更新される
    ※SampleRange Is Nothing Then の判定結果と、SampleRange.Addressによって変数の値が保持されていることを確認しています。

プロシージャのマクロの実行が完了しても、変数の値が保持されていることが、2回目の実行結果から確認できます。なお、Staticで宣言した変数がリセット(初期化)されるタイミングは、ブックを閉じたときモジュールにプロシージャを追加するなどのを変更をしたとき実行時エラーが発生したときです。

エクセルVBAを基本から体系的に理解していきたいなら、おすすめの一冊。

VBAの配列のスコープ(適用範囲)

VBAの変数・定数については値を1つ代入することができますが、ここからは複数の値を代入できる配列に関するスコープ(適用範囲)について解説します。配列と言っても変数の1つなので、難しく考える必要はありません。

なお、配列は定数で宣言することができないため、ここでは定数について考える必要はありません。(調べてみたところ、配列でArray関数やSplit関数を使って定数の配列のようなものは作れるようですが、変則的な方法となるのでここでは言及を控えておきます。)

プロシージャレベル配列のスコープ(適用範囲)

プロシージャの中で宣言する複数の値を代入することができる配列(変数)です。配列も変数の1つなので、動作は変数で紹介したものと同じです。

SubやFunctionから、End SubやEnd Functionのなかで、Dimステートメントをつけて宣言します。変数とちがう点は、宣言文の変数名に添え字をつけるところです。

静的配列では、宣言時に添え字で要素数を記入し、動的配列では”()”のみをつけておきます。なお、プロシージャで宣言した配列のスコープ(適用範囲)はプロシージャの範囲になるため、宣言したプロシージャ以外からの値の代入や、参照はできません。

なお、プロシージャレベルの配列は、プロシージャの処理が完了した時点で変数の値はリセット(初期化)されます。

配列については、別の記事でも紹介しているのでそちらも参考にしてください。

上記のリンクから参考記事を読むことができます。

プロシージャレベルの配列をあつかったサンプルマクロ

標準モジュール(配列のスコープ1)
Option Explicit

Sub プロシージャレベルの配列1() '①配列C・配列Dを宣言しているプロシージャ
    
    Dim i As Integer '繰り返し用の変数を宣言
    Dim 配列C(2) As Double '静的配列Cを宣言(要素数は3つ)
    Dim 配列D() As Variant '動的配列Dを宣言
    
    '静的配列Cに値を代入する
    配列C(0) = 164
    配列C(1) = 160
    配列C(2) = 161
    
    '静的配列Cに値を出力する
    For i = 0 To UBound(配列C) '静的配列の要素分だけ繰り返す
        Debug.Print 配列C(i) '代入された値をイミディエイトウィンドウに出力する
    Next i
    
     '動的配列Dに値を代入する
    ReDim 配列D(2, 2) As Variant '動的配列の再定義(3行3列の2次元配列)
    '名前を入力する
    配列D(0, 0) = "NOCCHi"
    配列D(1, 0) = "a-chan"
    配列D(2, 0) = "KASHIYUKA"
    
    '生年月日を入力する
    配列D(0, 1) = #9/20/1988#
    配列D(1, 1) = #2/15/1989#
    配列D(2, 1) = #12/23/1988#
    
    '血液型を入力する
    配列D(0, 2) = "Blood / A"
    配列D(1, 2) = "Blood / A"
    配列D(2, 2) = "Blood / A"
    
    '動的配列Dの値を出力する
    For i = 0 To UBound(配列D) '動的配列の要素分だけ繰り返す
        Debug.Print 配列D(i, 0) & " : " & 配列D(i, 1) & " : " & 配列D(i, 2) '代入された値をイミディエイトウィンドウに出力する
    Next i
    
End Sub
Sub プロシージャレベルの配列2() '②配列を宣言していないプロシージャ(変数が定義されていないエラーが発生する)
    
    Dim i As Integer
    
'    '静的配列Cに値を出力する
    For i = 0 To UBound(配列C) '静的配列の要素分だけ繰り返す
        Debug.Print 配列C(i) '代入された値をイミディエイトウィンドウに出力する
    Next i
    
    '動的配列Dの値を出力する
    For i = 0 To UBound(配列D) '動的配列の要素分だけ繰り返す
        Debug.Print 配列D(i, 0) & " : " & 配列D(i, 1) & " : " & 配列D(i, 2) '代入された値をイミディエイトウィンドウに出力する
    Next i

End Sub

こちらのサンプルマクロでは、標準モジュール1(プロシージャレベルの配列1)に2つのプロシージャを書いています。プロシージャレベルの配列1(①)では、静的配列である「配列C」と動的配列である「配列D」を宣言して、それぞれに値を代入したあとでイミディエイトウィンドウに出力してみます。

ここであつかっている静的配列の「配列C」は、1次元配列で3つの要素をもっていて、動的配列である「配列D」は3行×3列の要素をもつ2次元配列になります。

一方、プロシージャレベルの配列2(②)では、配列C・配列Dともに宣言文は書かずに、値の出力をするコードのみとしています。

既にマクロ内に実行結果をコメントアウトしていますが、プロシージャ①と②それぞれの実行結果をみていきましょう。

実行結果

プロシージャ①の実行結果

164
160
161
NOCCHi : 1988/09/20 : Blood / A
a-chan : 1989/02/15 : Blood / A
KASHIYUKA : 1988/12/23 : Blood / A

イミディエイトウィンドウに出力された内容です。上から3行が「配列C」の出力結果、それ以降が「配列D」の出力結果です。プロシージャ①で宣言した配列に値の代入と値の出力の処理を書いていますので、当然ながら問題なく実行が可能です。

プロシージャ②の実行結果

以下の画像のとおり「変数が定義されていない」エラーが発生します。

定数の宣言がされていないエラーメッセージの画像

プロシージャ①で宣言した「配列C」と「配列D」はいずれもプロシージャレベルのスコープ(適用範囲)ですので、プロシージャ②のなかに出力に指定した配列(変数)は存在していません。そのため実行時エラーが発生していて、スコープ(適用範囲)のとおりプロシージャ②からは値の代入も出力はできません。

モジュールレベル配列のスコープ(適用範囲)

プロシージャレベルの配列と異なり、モジュールの宣言セクションで宣言をする変数です。

宣言セクションで、Dimステートメントや、Privateステートメントをつかって宣言することでモジュールレベルの配列となります。

配列も変数の1つですので、宣言方法は同じですが宣言文の変数名に添え字をつけることで配列となります。なお、静的配列では、宣言時に添え字で要素数を記入し、動的配列では”()”のみをつけておきます。

プロシージャレベルの配列と異なる点として、同じモジュールに書かれた他のプロシージャからでも値の代入や参照ができます。なお、モジュールレベルの配列の有効期限は、宣言したモジュールの処理が完了するまでです。

モジュールレベルの配列のスコープ(適用範囲)をあつかったサンプルマクロ

以下の2つのモジュールで検証してみます。モジュールレベルでの検証になりますので、宣言セクションで配列を宣言するモジュールである①と、その配列に値を代入・出力の実行を試みる別のモジュール②の2つを作成して、それぞれを実行して動作を確認してみます。

標準モジュール(配列のスコープ1)
Option Explicit


Private M静的配列B(2) As String'モジュールレベルのスコープ(適用範囲)の静的配列
Private M動的配列B() As String 'モジュールレベルのスコープ(適用範囲)の動的配列

Sub モジュールレベルの配列1() ' ①モジュールレベルの配列にアクセスする
    
    'モジュールレベルのスコープ(適用範囲)の静的配列に値を代入
    M静的配列B(0) = "猪名寺乱太郎"
    M静的配列B(1) = "摂津ノきり丸"
    M静的配列B(2) = "福富しんべヱ"
    
    Debug.Print M静的配列B(0) & " と "; M静的配列B(1) & " と "; M静的配列B(2) & " は人気アニメのキャラクターです。"

    ReDim M動的配列B(2, 3)
    
    'モジュールレベルのスコープ(適用範囲)の動的配列に値を代入
        
    Dim i As Integer '繰り返し処理用変数
    For i = 0 To UBound(M静的配列B) 'M静的配列Bの要素分だけ繰り返す
        Select Case i '変数iによって分岐処理
            
            Case 0 '乱太郎(名前、身長、年齢、体重)データを代入する
                M動的配列B(i, 0) = M静的配列B(i)
                M動的配列B(i, 1) = "136㎝"
                M動的配列B(i, 2) = "10歳"
                M動的配列B(i, 3) = "30㎏"
            
            Case 1 'きり丸(名前、身長、年齢、体重)データを代入する
                M動的配列B(i, 0) = M静的配列B(i)
                M動的配列B(i, 1) = "140㎝"
                M動的配列B(i, 2) = "10歳"
                M動的配列B(i, 3) = "34㎏"
            
            Case 2 'しんべヱ(名前、身長、年齢、体重)データを代入する
                M動的配列B(i, 0) = M静的配列B(i)
                M動的配列B(i, 1) = "125㎝"
                M動的配列B(i, 2) = "10歳"
                M動的配列B(i, 3) = "67.5㎏"
        
        End Select
    Next i
    
    'M動的配列Bの値を出力する
    For i = 0 To UBound(M動的配列B, 2) - 1
        Debug.Print M動的配列B(i, 0) & " は身長が " & M動的配列B(i, 1) & " で年齢は " & M動的配列B(i, 2) & " で体重は " & M動的配列B(i, 3) & "です。"
    Next i

End Sub

宣言セクションでは静的・動的の2つの配列を宣言しています。これらの配列は「Private」ステートメントをつけているため、モジュールのスコープ(適用範囲)となることから配列を宣言しているモジュールにあるプロシージャからであれば値の代入や出力ができます。

さて、マクロの動作のながれについての説明ですが、配列の「M静的配列B」の添え字は(2)を指定しているため、3つの要素を持てる配列になっています。ここに3つの文字列(キャラクターの名前)を代入したあと、イミディエイトウィンドウに「M静的配列B」の値を出力してみます。

続いて、もう一つの配列である「M動的配列B」は、3行×4列の2次元配列に再定義しています。そしてこの「M動的配列B」にそれぞれのキャラクターのプロフィール情報(身長・年齢・体重)を代入していきます。
こちらも先ほどと同じく、「M動的配列B」の値をイミディエイトウィンドウに出力してみます。

標準モジュール(配列のスコープ2)
Option Explicit

Sub モジュールレベルの配列2() '②モジュールレベルの配列にアクセスする
        
    'モジュールレベルのスコープ(適用範囲)の静的配列に値を代入
    M静的配列B(0) = "ルパン"
    M静的配列B(1) = "次元大介"
    M静的配列B(2) = "五右衛門"

    'モジュールレベルのスコープ(適用範囲)の静的配列の値を出力
    Debug.Print M静的配列B(0) & " と "; M静的配列B(1) & " と "; M静的配列B(2) & " は人気アニメのキャラクターです。"
    
    'モジュールレベルのスコープ(適用範囲)の動的配列を再定義
    ReDim M動的配列B(2, 1)
    
    'モジュールレベルのスコープ(適用範囲)の動的配列に値を代入
    M動的配列B(0, 0) = M静的配列B(0)
    M動的配列B(0, 1) = "ワルサーP38"
    M動的配列B(1, 0) = M静的配列B(1)
    M動的配列B(1, 1) = "S&W M19 コンバット・マグナム"
    M動的配列B(2, 0) = M静的配列B(2)
    M動的配列B(2, 1) = "斬鉄剣"
    
    'モジュールレベルのスコープ(適用範囲)の静的配列の値を出力
    Debug.Print M動的配列B(0, 0) & " の武器の名前は " & M動的配列B(0, 1)
    Debug.Print M動的配列B(1, 0) & " の武器の名前は " & M動的配列B(1, 1)
    Debug.Print M動的配列B(2, 0) & " の武器の名前は " & M動的配列B(2, 1)

End Sub

こちらは先ほどのモジュールとは別のモジュール「標準モジュール(配列のスコープ2)
」に処理を書いています。

こちらのプロシージャで実行する処理の内容は、使用する値はちがうものの、配列を宣言したモジュールとほぼ同じで「M静的配列B」と「M動的配列B」に値の代入と出力を試みます。

実行結果

標準モジュール(配列のスコープ1)

猪名寺乱太郎 と 摂津ノきり丸 と 福富しんべヱ は人気アニメのキャラクターです。
猪名寺乱太郎 は身長が 136㎝ で年齢は 10歳 で体重は 30㎏です。
摂津ノきり丸 は身長が 140㎝ で年齢は 10歳 で体重は 34㎏です。
福富しんべヱ は身長が 125㎝ で年齢は 10歳 で体重は 67.5㎏です。

イミディエイトウィンドウの実行結果の1行目は「M静的配列B」の値を出力してみた結果です。2行目から4行目までが「M動的配列B」の値を出力した結果です。

これによりモジュールで宣言した2つの配列が、プロシージャで利用できていることが確認できました。

標準モジュール(配列のスコープ2)

コンパイルエラーが発生します。
以下の画像のように「Sub または Function が定義されていません。」のエラーが表示されます。

コンパイルエラー「SubまたはFunctionが定義されていません」エラーメッセージ画像

この結果から、モジュールレベルのスコープ(適用範囲)で宣言した配列は、他のモジュールから利用できないことが確認できました。

パブリックレベルの配列のスコープ(適用範囲)

プロシージャレベルの変数(配列)と異なり、モジュールの宣言セクションで宣言をする変数です。宣言セクションで、Publicステートメントをつかって宣言することでパブリックレベルの配列となります。

配列は変数の1つですので、変数の宣言方法と同じですが、添え字を書く必要があります。添え字とは配列がもつ要素数ですので、宣言する配列の大きさを添え字で指定していることになります。
この大きさを宣言時に配列名の右側に書きますが、具体的な数値で指定してあるものを静的配列、”()”だけを指定してあるものを動的配列と呼びます。

パブリックレベルの変数(配列)は、グローバル変数とも呼ばれ、対象となる変数を宣言したモジュール外からの値の代入や参照が可能です。なお、パブリックレベルの変数の有効期限はブック(ファイル)を閉じるまでです。

パブリックレベルの配列のスコープ(適用範囲)をあつかったサンプルマクロ

モジュールレベルの時と同様に、2つのモジュールを作成して検証してみます。

パブリックレベルでの検証になりますので、宣言セクションで配列を宣言するモジュールである①と、その配列に値を代入・出力の実行を試みる別のモジュール②の2つを作成して、それぞれを実行して動作を確認してみます。

標準モジュール(配列のスコープ1)
Option Explicit

Public G静的配列A(2) As String 'パブリックレベルのスコープ(適用範囲)の静的配列
Public G動的配列A() As String 'パブリックレベルのスコープ(適用範囲)の動的配列

Sub パブリックレベルの配列1() ' ①パブリックレベルの配列にアクセスする

    'パブリックレベルのスコープ(適用範囲)の静的配列に値を代入
    G静的配列A(0) = "有馬"
    G静的配列A(1) = "草津"
    G静的配列A(2) = "下呂"
    
    'パブリックレベルのスコープ(適用範囲)の静的配列の値を出力する
    Debug.Print G静的配列A(0); "、"; G静的配列A(1); "、"; G静的配列A(2); "と言えば日本3名泉である。"
    
    'パブリックレベルのスコープ(適用範囲)の動的配列に値を代入
    ReDim G動的配列A(2, 1) '3行×2列の配列に再定義
    Dim i As Integer
    
    For i = 0 To UBound(G静的配列A, 1)
        G動的配列A(i, 0) = G静的配列A(i) & "温泉"
        
        If i = 0 Then
            G動的配列A(i, 1) = "兵庫県"
        
        ElseIf i = 1 Then
            G動的配列A(i, 1) = "群馬県"
        
        ElseIf i = 2 Then
            G動的配列A(i, 1) = "岐阜県"
        
        End If
    Next i

    'パブリックレベルのスコープ(適用範囲)の動的配列の値を出力する
    i = 0
    Do Until i = UBound(G動的配列A, 1) + 1 'iが3になるまで繰り返す
        Debug.Print G動的配列A(i, 0); " は "; G動的配列A(i, 1); " にあります。"
        i = i + 1 'カウンタ変数の更新
    Loop
    

End Sub

上記のモジュールでは、宣言セクションにてパブリックレベルのスコープ(適用範囲)の配列を2つ宣言しています。配列名からでもわかりますが、「G静的配列A」は静的配列で、「G動的配列A」は動的配列です。

「G静的配列A」は、文字列型で3つの要素をもつことができる配列であるため、文字列を代入してイミディエイトウィンドウに値の出力する処理をしています。

また「G動的配列A」は、3行×2列の配列に要素の大きさを再定義したのち、繰り返しと条件分岐をつかって1列目には「G静的配列A」の値と文字列”温泉”をつなぎ合わせたものを代入し、2列目には新たに文字列を代入して、最後のDo Loop文で値の出力処理をしています。

標準モジュール(配列のスコープ2)
Option Explicit

Sub パブリックレベルの配列2() '②パブリックレベルの配列にアクセスする
        
    'パブリックレベルのスコープ(適用範囲)の静的配列に値を代入
    G静的配列A(0) = "松島"
    G静的配列A(1) = "天橋立"
    G静的配列A(2) = "宮島"

   'パブリックレベルのスコープ(適用範囲)の静的配列の値を出力する
    Debug.Print G静的配列A(0); "、"; G静的配列A(1); "、"; G静的配列A(2); "と言えば日本3景である。"
    
    'パブリックレベルのスコープ(適用範囲)の動的配列を再定義
    ReDim G動的配列A(2, 1)
    
    'パブリックレベルのスコープ(適用範囲)の動的配列に値を代入
    Dim i As Integer
    
    For i = 0 To UBound(G静的配列A, 1)
        G動的配列A(i, 0) = G静的配列A(i)
        
        If i = 0 Then
            G動的配列A(i, 1) = "宮城県"
        
        ElseIf i = 1 Then
            G動的配列A(i, 1) = "京都府"
        
        ElseIf i = 2 Then
            G動的配列A(i, 1) = "広島県"
        
        End If
    Next i

    'パブリックレベルのスコープ(適用範囲)の動的配列の値を出力する
    i = 0
    Do Until i = UBound(G動的配列A, 1) + 1 'iが3になるまで繰り返す
        Debug.Print G動的配列A(i, 0); " は "; G動的配列A(i, 1); " にあります。"
        i = i + 1 'カウンタ変数の更新
    Loop

End Sub

こちらのモジュールでは、宣言セクションで配列の宣言は行っていません。ですが、標準モジュール(配列のスコープ1)においてパブリックレベルのスコープ(適用範囲)の配列が宣言されていますので、「G静的配列A」と「G動的配列A」をあつかうことができます。

このモジュール(配列のスコープ2)で処理している内容は、代入する値はちがっているものの、標準モジュール(配列のスコープ1)と同じですので詳しい説明は割愛しますが、パブリックレベルのスコープ(適用範囲)の配列は、宣言したモジュール以外からでも値の代入と出力ができることが確かめられるかを見てみたいと思います。

実行結果

標準モジュール(配列のスコープ1)

有馬、草津、下呂と言えば日本3名泉である。
有馬温泉 は 兵庫県 にあります。
草津温泉 は 群馬県 にあります。
下呂温泉 は 岐阜県 にあります。

宣言セクションで2つの配列を宣言し、プロシージャの中で値を代入して、イミディエイトウィンドウに出力しています。
出力された結果の1行目が「G静的配列A」に代入された値を、2から4行目までが「G動的配列A」に代入した値をつかって文章を表示することができました。

標準モジュール(配列のスコープ2)

松島、天橋立、宮島と言えば日本3景である。
松島 は 宮城県 にあります。
天橋立 は 京都府 にあります。
宮島 は 広島県 にあります。

プロシージャ、モジュールレベルのケースと実行結果は異なり、宣言セクションで配列は宣言してなくとも、パブリックレベルのスコープ(適用範囲)の配列に対して、値の代入と出力ができています。

出力された結果の1行目が「G静的配列A」に代入された値を、2から4行目までが「G動的配列A」に代入した値をつかって文章を表示することができました。

PCでスキルアップをしたい・Excelをしっかり学んで社内の評価を高めたい人は必見!
実務をプロから学べる「ユースフル」は講座の動画は永年見放題。安心のQ&A機能で分からないを解決。

VBAの構造体(ユーザー定義型)のスコープ(適用範囲)

これまで変数(配列を含め)・定数に関するスコープ(適用範囲)について紹介してきましたが、ここではVBAにおける構造体(ユーザー定義型)のスコープ(適用範囲)について触れていきたいと思います。

構造体(ユーザー定義型)をひと言で言えばオブジェクトの1つです。と言われても分かりにくいと思いますが、端的に言えばデータを格納できる箱の一種と考えると良いでしょう。変数との大きな違いは、変数は1つにつき1つの値のみですが、構造体(ユーザー定義型)は、データ型(整数型や文字列型など)が異なるものを複数個代入することができます。

構造体(ユーザー定義型)は、宣言セクションのみで宣言ができます。宣言方法は、Typeステートメントをつかって宣言します。

●構造体(ユーザー定義型)の宣言方法

[Public|Private]Type 構造体名
 要素名1
 要素名2
 要素名3
End Type

※標準モジュールやクラスモジュールで、「Public」や「Private」キーワードを省略した場合は、パブリックレベルとしてあつかわれます。他のモジュールからの影響をうけないようにするためには、「Private」キーワードを指定してください。

構造体(ユーザー定義型)は、以下の記事でも紹介していますので、宣言方法や使い方はそちらをご確認ください。

上記のリンクから参考記事を読むことができます。

モジュールレベルの構造体(ユーザー定義型)のスコープ(適用範囲)

構造体(ユーザー定義型)は宣言セクションでのみ宣言できるため、宣言セクションで「Private」を使用することで構造体(ユーザー定義型)にモジュールレベルのスコープ(適用範囲)を指定することができます。

これにより、指定された構造体(ユーザー定義型)はモジュールの中だけであつかえるものとなります。

モジュールレベルの配列のスコープ(適用範囲)をあつかったサンプルマクロ

標準モジュールを2つ準備し、宣言セクションでモジュールレベルのスコープ(適用範囲)を指定した構造体(ユーザー定義型)が、宣言したモジュールで利用できることと、他のモジュールで利用できるかをサンプルマクロを実行して確認してみます。

標準モジュール(ユーザー定義型のスコープ1)
Option Explicit

'宣言セクションでユーザー定義型(構造体)を宣言する(モジュールレベルのためPrivateを使用)
Private Type ユーザー定義型A
    Type_名前 As String
    Type_試験日 As Date
    Type_国語 As Integer
    Type_数学 As Integer
    Type_英語 As Integer
    Type_理科 As Integer
    Type_社会 As Integer
End Type

Sub ユーザー定義型に値の代入と出力1()
    
    'ユーザー定義型A(構造体)をプロシージャで使用するための宣言をする
    Dim テスト結果 As ユーザー定義型A
    
    'ユーザー定義型(構造体)をWithステートメントでまとめて値を代入する
    With テスト結果
        .Type_名前 = "キキ"
        .Type_試験日 = #2/2/2010#
        .Type_国語 = 60
        .Type_数学 = 55
        .Type_英語 = 72
        .Type_理科 = 85
        .Type_社会 = 28
    
    'ユーザー定義型(構造体)の値を出力する
        Debug.Print .Type_名前; "さんのテストの結果"
        Debug.Print "テスト実施日: "; .Type_試験日
        Debug.Print .Type_国語
        Debug.Print .Type_数学
        Debug.Print .Type_英語
        Debug.Print .Type_理科
        Debug.Print .Type_社会
    
    End With

End Sub

こちらのモジュールでは、宣言セクションでユーザ定義型Aという名前のユーザー定義型(構造体)を「Private」で宣言しています。そして、同じモジュールのプロシージャである「ユーザー定義型に値の代入と出力1」のなかで値の代入と出力を実行してみます。

標準モジュール(ユーザー定義型のスコープ2)
Sub ユーザー定義型に値の代入と出力2()
   
  'ユーザー定義型A(構造体)をプロシージャで使用するための宣言をする
    Dim テスト結果 As ユーザー定義型A
    
    With テスト結果
        .Type_名前 = "ウルスラ"
        .Type_試験日 = #2/2/2005#
        .Type_国語 = 65
        .Type_数学 = 45
        .Type_英語 = 77
        .Type_理科 = 68
        .Type_社会 = 35
    
        Debug.Print .Type_名前; "さんのテストの結果"
        Debug.Print "テスト実施日: "; .Type_試験日
        Debug.Print .Type_国語
        Debug.Print .Type_数学
        Debug.Print .Type_英語
        Debug.Print .Type_理科
        Debug.Print .Type_社会
    
    End With

End Sub

こちらのモジュールでは、宣言セクションでユーザー定義型(構造体)の宣言はおこなっていません。プロシージャ「ユーザー定義型に値の代入と出力2」のなかでユーザー定義型Aをつかうための宣言はしていますが、モジュール「ユーザー定義型のスコープ1」と同様に、「Private」で指定したユーザ定義型Aの利用ができるか値の代入と出力を試みます。

実行結果

標準モジュール(ユーザー定義型のスコープ1)

キキさんのテストの結果
テスト実施日: 2010/02/02
60
55
72
85
28

標準モジュール(ユーザー定義型のスコープ2)

コンパイルエラーが発生します。
以下の画像のように「ユーザ定義型は定義されていません。」のエラーが表示されます。

コンパイルエラー「ユーザ定義型は定義されていません」エラーメッセージ画像

これにより、指定された構造体(ユーザー定義型)は、モジュールの中だけであつかえるものであることが確認できました。

パブリックレベルの構造体(ユーザー定義型)のスコープ(適用範囲)

構造体(ユーザー定義型)は、宣言セクションでのみ宣言できるため、宣言セクションで「Public」を使用することで構造体(ユーザー定義型)にパブリックレベルのスコープ(適用範囲)を指定することができます。

これにより、指定された構造体(ユーザー定義型)は、他のモジュールからもあつかえるものとなります。

パブリックレベルの配列のスコープ(適用範囲)をあつかったサンプルマクロ

モジュールレベルのときの検証と同じように、標準モジュールを2つ準備し、宣言セクションでパブリックレベルのスコープ(適用範囲)が指定された構造体(ユーザー定義型)が、いずれのモジュールからも利用ができることの確認をしてみます。

標準モジュール(ユーザー定義型のスコープ1)
Option Explicit

'宣言セクションでユーザー定義型(構造体)を宣言する(パブリックレベルのためPublicを使用)
Public Type ユーザー定義型B
    Type_名前 As String
    Type_試験日 As Date
    Type_国語 As Integer
    Type_数学 As Integer
    Type_英語 As Integer
    Type_理科 As Integer
    Type_社会 As Integer
End Type

Sub ユーザー定義型に値の代入と出力1_2()'①
    
    'ユーザー定義型B(構造体)をプロシージャで使用するための宣言をする
    Dim テスト結果 As ユーザー定義型B
    
    'ユーザー定義型(構造体)をWithステートメントでまとめて値を代入する
    With テスト結果
        .Type_名前 = "ソノ"
        .Type_試験日 = #2/2/1997#
        .Type_国語 = 69
        .Type_数学 = 75
        .Type_英語 = 70
        .Type_理科 = 50
        .Type_社会 = 35
    
    'ユーザー定義型(構造体)の値を出力する
        Debug.Print .Type_名前; "さんのテストの結果"
        Debug.Print "テスト実施日: "; .Type_試験日
        Debug.Print .Type_国語
        Debug.Print .Type_数学
        Debug.Print .Type_英語
        Debug.Print .Type_理科
        Debug.Print .Type_社会
    
    End With

End Sub

こちらのモジュールでは、宣言セクションでユーザー定義型Bという名前のユーザー定義型(構造体)を「Public」で宣言しています。そして、同じモジュールのプロシージャである「ユーザー定義型に値の代入と出力1_2」のなかで値の代入と出力を実行してみます。

標準モジュール(ユーザー定義型のスコープ2)
Sub ユーザー定義型に値の代入と出力2_2()'②

     'ユーザー定義型A(構造体)をプロシージャで使用するための宣言をする
    Dim テスト結果 As ユーザー定義型B
    
    With テスト結果
        .Type_名前 = "コキリ"
        .Type_試験日 = #2/2/1986#
        .Type_国語 = 80
        .Type_数学 = 92
        .Type_英語 = 86
        .Type_理科 = 100
        .Type_社会 = 45
    
        Debug.Print .Type_名前; "さんのテストの結果"
        Debug.Print "テスト実施日: "; .Type_試験日
        Debug.Print .Type_国語
        Debug.Print .Type_数学
        Debug.Print .Type_英語
        Debug.Print .Type_理科
        Debug.Print .Type_社会
    
    End With

End Sub

こちらのモジュールでは、宣言セクションでユーザー定義型(構造体)の宣言はおこなっていません。プロシージャ「ユーザー定義型に値を代入と出力2_2」のなかでは、ユーザー定義型(構造体)をつかうための宣言はしていますが、先ほどのモジュール「ユーザー定義型のスコープ1_2」と同様に値の代入と出力を試みます。

実行結果

標準モジュール(ユーザー定義型のスコープ1)

ソノさんのテストの結果
テスト実施日: 1997/02/02
69
75
70
50
35

このモジュールでパブリックレベルのユーザ定義型(構造体)を宣言していて、値を代入・出力するプロシージャも同じモジュールのなかにあるので、スコープ(適用範囲)の指定どおり、正常に値の出力ができていることがわかります。

標準モジュール(ユーザー定義型のスコープ1)

コキリさんのテストの結果
テスト実施日: 1986/02/02
80
92
86
100
45

こちらのモジュールでは、ユーザ定義型(構造体)を宣言していませんが、「Public」で指定されたユーザ定義型(構造体)に対して、値を代入・出力することができていることから、スコープ(適用範囲)の指定どおり、宣言したモジュール以外からも、ユーザ定義型の利用ができていることがわかります。

ユーザ定義型(構造体)と使用宣言のための変数でスコープが異なる場合

ユーザ定義型は、モジュールの宣言セクションにおいて「Type」ステートメントで宣言するとお伝えしました。さらにスコープ(適用範囲)を指定するためには、「Public」または「Private」をつければ良いことになります。

それでは、以下のような宣言が行われた場合はどうなるでしょうか。

Option Explicit

'宣言セクションでユーザー定義型(構造体)を宣言する(モジュールレベルのためPrivateを使用)
Private Type ユーザー定義型A
    Type_名前 As String
    Type_試験日 As Date
    Type_国語 As Integer
    Type_数学 As Integer
    Type_英語 As Integer
    Type_理科 As Integer
    Type_社会 As Integer
End Type

'宣言セクションでユーザー定義型(構造体)を宣言する(パブリックレベルのためPublicを使用)
Public Type ユーザー定義型B
    Type_名前 As String
    Type_試験日 As Date
    Type_国語 As Integer
    Type_数学 As Integer
    Type_英語 As Integer
    Type_理科 As Integer
    Type_社会 As Integer
End Type

'Privateで宣言したユーザー定義型A(構造体)を代入する変数を宣言する(パブリックレベルのためPublicを使用)
Public UserA As ユーザー定義型A

'Publicで宣言したユーザー定義型B(構造体)を代入する変数を宣言する(モジュールレベルのためPrivateを使用)
Private UserB As ユーザー定義型B

このモジュールの宣言セクションには、「ユーザ定義型A」と「ユーザ定義型B」という名前の2つのユーザ定義型(構造体)が宣言されています。さらに、それぞれを使用するための変数とて「UserA」と「UserB」も宣言しています。

注目しておきたい点は、ユーザ定義型(構造体)の宣言時と変数の宣言時のスコープ(適用範囲)が異なっている点です。スコープ(適用範囲)をパブリックレベルをモジュールレベルに、モジュールレベルをパブリックレベルにクロスさせたような状態にしています。(下記のイメージ図を参照)

モジュールとプロシージャ、ユーザ定義型(構造体)・変数のイメージ図
スコープ(適用範囲)の検証イメージ図

具体的には、「ユーザ定義型A」はPrivateで宣言していますが、それを代入した変数である「UserA」はPublicで宣言しており、「ユーザ定義型B」はPublicで宣言していますが、変数の「UserB」はPrivateでの宣言にしています。

モジュールレベルであれ、パブリックレベルであれ、これらを宣言したモジュールでは利用可能であるため、宣言したモジュール以外のモジュールからの利用可否をまとめました。

モジュール1で宣言したオブジェクトと変数 スコープ(適用範囲)の指定 宣言していないモジュール2からの利用
ユーザ定義型A Private 不可
ユーザ定義型B Public 可能
UserA
UserB Private 不可

変数「UserA」を他のモジュールから利用する場合は、値の代入や出力が直接できますが、「ユーザ定義型B」を他のモジュールから利用する場合は、通常のユーザ定義型(構造体)を利用するときと同様に宣言セクションもしくは、プロシージャでユーザ定義型(構造体)を使用するための変数の宣言文を書く必要があります。

スコープ(適用範囲)の結果をみると「ユーザ定義型」と「変数」は、別ものと考えて大丈夫そうだね。

エクセル・ワード・パワーポイントのオンライン学習
料金負担が安いのになんど見ても追加費用は不要!
安心の環境でしっかりとスキルをつけたいならPCHack

↓↓↓くわしくはこちら↓↓↓

VBAのクラス変数のスコープ(適用範囲)

これまでは標準モジュールに宣言した変数、定数、ユーザ定義型(構造体)などのスコープ(適用範囲)について紹介してきました。ここでは、クラスモジュールで宣言した変数でスコープ(適用範囲)を指定する方法と変数の動作について紹介していきます。

クラスの変数のスコープ(適用範囲)の話題に入る前に、エクセルVBAには、標準モジュールやブック・シート・フォームモジュールの他に、クラスモジュールがあります。クラスモジュールの作り方や使い方についてはここで詳しくは書きませんが、クラスモジュールも、他のモジュールと同様にプロシージャを書くことや、変数の宣言ができ、スコープ(適用範囲)を指定することができます。

クラスモジュールの宣言セクションで宣言した、パブリックやモジュールレベルの変数のことを、クラスのプロパティと呼びます。また、クラスモジュールに書いたSubプロシージャや、Functionプロシージャのことをメソッドと呼びますが、メソッドでも標準モジュールと同じように変数の宣言ができます。

クラスのなかで宣言した変数も、標準モジュールと同じく、指定したスコープ(適用範囲)の種類によっては、外部から値の代入や出力ができます。しかし、大規模で複雑なマクロでは、どこからでも変数の値を書きかえられてしまうことは、システム開発においてマクロ全体のリスクになると考えられるため、スコープ(適用範囲)は必要最低限としておくといった考え方があります。

エクセルVBAでは、原則としてこの方針がありながらも、隠された変数(プロパティ)の値を設定したり、取得するための専用口としてプロパティプロシージャと呼ばれるものを使うことがありますので、これについてものちほど紹介します。

それでは、クラスにおけるスコープ(適用範囲)を指定した変数が、どのような動きになるかについてサンプルマクロを見ながら解説していきます。

クラスのメソッドで宣言した変数のスコープ(適用範囲)

こちらは標準モジュールのプロシージャで宣言した、プロシージャレベルの変数と同じです。クラスにおいてもプロシージャのなかで宣言した変数は、プロシージャのなかでのみ値の代入や出力ができます。

クラスのメソッドで宣言した変数をあつかったサンプルマクロ

標準モジュール1(変数のスコープ_クラス)
Option Explicit

Sub クラス変数に値を代入して出力する1() '①

'クラスを使うための処理
Dim クラス As ポケモンクラス
Set クラス = New ポケモンクラス

'クラス内のプロシージャ(メソッド)を呼び出す①
クラス.プロシージャレベルの変数に値の代入をするA ("チコリータ")

'クラス内のプロシージャ(メソッド)を呼び出す②
クラス.プロシージャレベルの変数に値の代入をするB ("ニョロモ")

End Sub

こちらは、クラスモジュールを使うための処理とクラスに書いたプロシージャ(メソッド)を呼び出して、クラスのなかで宣言した変数に値の代入と出力をこころみます。なお、それぞれのメソッドを呼び出すときに引数を指定していますので、この値をクラスのなかの変数に代入します。

クラスモジュール(ポケモンクラス)
Option Explicit

Sub プロシージャレベルの変数に値の代入をするA(Val As String) 'プロシージャの中で変数を宣言している

    'クラスのプロシージャレベルの変数を宣言する
    Dim クラスの変数 As String

    'クラスのプロシージャレベルの変数に値を代入する
    クラスの変数 = Val

    'クラスのプロシージャレベルの変数に値を出力する
    Debug.Print クラスの変数

End Sub

Sub プロシージャレベルの変数に値の代入をするB(Val As String) 'プロシージャの中で変数を宣言していない

    'クラスのプロシージャレベルの変数に値を代入する
    クラスの変数 = Val
    
    'クラスのプロシージャレベルの変数に値を出力する
    Debug.Print クラスの変数

End Sub

こちらはクラスモジュールに書いたコードです。プロシージャ(メソッド)を2つ準備していて、それぞれのプロシージャ名で「プロシージャレベルの変数に値の代入をする」Aと同名のBの2つを作成しています。
いずれのプロシージャ(メソッド)も処理している内容は、標準モジュール1から呼び出されたときに受け取った引数を変数名「クラスの変数」に代入し、イミディエイトウィンドウに変数の値を出力するといったシンプルなものです。
ただし、Aのプロシージャでは「クラスの変数」を宣言しており、Bでは変数の宣言をしていないという差分があります。

実行結果を確認してみます。

実行結果

クラスモジュール(プロシージャレベルの変数に値の代入をするA)

チコリータ

クラスモジュール(プロシージャレベルの変数に値の代入をするB)
変数の宣言がされていないエラーメッセージの画像

こちらのプロシージャでは、上の画像のとおり「クラスの変数」が宣言されていないことから「変数が定義されていない」ことによるエラーとなりました。

この結果により、「クラスの変数」のスコープ(適用範囲)がプロシージャのなかに限定されていることが確認できました。

クラスでモジュールレベルの宣言をした変数(プロパティ)のスコープ(適用範囲)

今度は、クラスモジュールの宣言セクションで宣言した変数についてですが、標準モジュールと同じようにクラスモジュールで「Private」を使って宣言します。しかし、クラスモジュールはそれ単体ではなく、標準モジュールから呼び出して動かしますので、モジュールを2つつかうことになります。しかし、変数の宣言で指定したスコープ(適用範囲)は、モジュールレベルの変数(プロパティ)ですので、その変数が有効となる範囲は、変数を宣言したクラスモジュールとなります。

つまり、クラスモジュール内で変数を利用するのであれば問題なく動作しますが、スコープ(適用範囲)の外側にある、標準モジュールで変数を利用しようとするとエラーが発生します。

これについては、本章の冒頭でも少し触れましたが、クラスモジュールで宣言したモジュールレベルの変数(プロパティ)は、「プロパティプロシージャ」と呼ばれるものを使う方法で標準モジュールから利用ができます。

少し複雑な話になりましたが、ここで紹介するサンプルマクロの内容をまとめると以下になります。

  1. クラスモジュールで宣言した変数(プロパティ)をクラスモジュールで利用する
  2. クラスモジュールで宣言した変数(プロパティ)を標準モジュールから利用する
  3. クラスモジュールで宣言した変数(プロパティ)を標準モジュールからプロパティプロシージャを経由して利用する

上から順番にサンプルマクロを見ていきましょう。

クラスモジュールで宣言した変数(プロパティ)をあつかったサンプルマクロ

①クラスモジュールで宣言した変数(プロパティ)をクラスモジュールで利用する

まずは、宣言と処理が同じクラスモジュール内で完結しているものです。

言わずもがなですが、スコープ(適用範囲)がモジュールレベルの変数(プロパティ)を宣言しているクラスモジュールで、その変数をあつかうことはできます。

標準モジュール(変数のスコープ_クラス)
Option Explicit

Sub クラス変数に値を代入して出力する1() '①クラスのメソッドで処理をする場合はエラーは発生せず。
    
    'クラスを使うための処理
    Dim クラスを使う As ポケモンクラス
    Set クラスを使う = New ポケモンクラス
    
    'クラスのメソッドを呼び出す
    クラスを使う.C1_メソッド

End Sub

クラスモジュールで変数を宣言するため、標準モジュールでは変数の宣言はしていません。クラスを使うための宣言をして、クラスに書いた処理(C1_メソッド)を呼び出します。

クラスモジュール(ポケモンクラス)
Option Explicit

'モジュールレベルの変数(プロパティ)の宣言
'変数名の「M」はモジュールレベルの意味です。
Private Mポケモン_ As String

Sub C1_メソッド() 'クラスで宣言したモジュールレベルの変数(プロパティ)に値を代入・出力する
    
    'クラスで宣言したモジュールレベルの変数(プロパティ)に対する処理
    Mポケモン_ = "バタフリー" '値の代入
    Debug.Print Mポケモン_ '値の出力

End Sub

クラスモジュールの宣言セクションで、変数名「Mポケモン_」を「Private」をつけてモジュールレベルで変数で宣言しています。標準モジュール(変数のスコープ_クラス)が実行されると、クラスを使うための処理がおこなわれたあと、クラス内のSubプロシージャである「C1_メソッド」が呼ばれます。

変数を宣言したモジュールと、値の代入、出力をするモジュールが同一であるため、正常動作として以下が出力されます。

実行結果

イミディエイトウィンドウに以下が表示されます。

バタフリー

引数に指定した値が変数に代入されていることが確認できました。

②クラスモジュールで宣言した変数(プロパティ)を標準モジュールから利用する

続いて、クラスモジュールで変数を宣言し、標準モジュール側で変数に値の代入と出力を実行しているマクロです。クラスモジュールで宣言した変数のスコープ(適用範囲)は、モジュールレベルであるため、標準モジュールから変数を見ることができず、値の代入や出力ができません。

そのため、マクロを実行するとエラーが発生します。

標準モジュール(変数のスコープ_クラス)
Option Explicit

Sub クラス変数に値を代入して出力する2() '②標準モジュールからクラスのプロパティに処理する場合はエラーが発生する。
    
    'クラスを使うための処理
    Dim クラスを使う As ポケモンクラス
    Set クラスを使う = New ポケモンクラス
    
    'クラスで宣言したモジュールレベルの変数(プロパティ)に対する処理
    クラスを使う.Mポケモン_ = "ヤドン"
    Debug.Print クラスを使う.Mポケモン_
    
End Sub
クラスモジュール(ポケモンクラス)
Option Explicit

'モジュールレベルの変数(プロパティ)の宣言
'変数名の「M」はモジュールレベルの意味です。
Private Mポケモン_ As String
クラスモジュールの宣言セクションで”Private”をつけて変数を宣言します。

実行結果

コンパイルエラーが発生します。

下の画像のように「メソッドまたはデータ メンバーが見つかりません。」のエラーメッセージが表示されます。

データメンバが見つからないエラーメッセージの画像

クラスモジュールで宣言した変数(プロパティ)は、標準モジュールからは見えないためクラスモジュールのメンバである変数「Mポケモン_」が見つからない旨のメッセージとなります。

③クラスモジュールで宣言した変数(プロパティ)を標準モジュールからプロパティプロシージャを経由して利用する

さいごに、クラスモジュールで変数を宣言し、プロパティプロシージャを経由して標準モジュール側で変数に値の代入と出力を実行しているマクロです。クラスモジュールで宣言した変数のスコープ(適用範囲)は、モジュールレベルであるため、標準モジュールから変数を見ることができませんが、同じモジュールに属するプロパティプロシージャを一度経由することで値の代入や出力が実行できるようになります。このように外部からのアクセスできないスコープ(適用範囲)の変数に、特別な窓口を設けて変数(プロパティ)に値を設定するプロシージャをセッター、変数の値を取得するプロシージャをゲッターと呼びます。

セッターやゲッターを経由する処理のイメージは以下のとおりです。

プロパティプロシージャを経由する処理のイメージ

それでは、セッターとゲッターを使ったサンプルマクロを見ていきましょう。

標準モジュール(変数のスコープ_クラス)
Option Explicit

Sub クラス変数に値を代入して出力する3() '③標準モジュールからプロパティプロシージャを経由することで実行ができる。
    
    'クラスを使うための処理
    Dim クラスを使う As ポケモンクラス
    Set クラスを使う = New ポケモンクラス
    
    'クラスで宣言したモジュールレベルの変数(プロパティ)に対する処理
    クラスを使う.ポケモン名 = "タッツー"
    Debug.Print クラスを使う.ポケモン名
    
End Sub

標準モジュールでは、変数の宣言はしていません。クラスを使うための処理を実行したあとで、プロパティプロシージャのセッターを呼び出しています。なお、セッターを呼び出すときに引数として代入したい値として”タッツー”を渡しています。そして最後にイミディエイトウィンドウにゲッターから受け取った値を出力する動作です。

クラスモジュール(ポケモンクラス)
Option Explicit

'モジュールレベルの変数(プロパティ)の宣言
'変数名の「M」はモジュールレベルの意味です。
Private Mポケモン_ As String

Property Let ポケモン名(ByVal Val As String)

    Mポケモン_ = Val

End Property
Property Get ポケモン名() As String
    
    ポケモン名 = Mポケモン_
    
End Property

クラスモジュールの宣言セクションで変数名「Mポケモン_」を宣言しています。その他にプロパティプロシージャのセッターとゲッターを書いています。セッターとゲッターはペアにすること、プロシージャ名を同じにします。

●Property Let ポケモン名(ByVal Val As String)

プロパティプロシージャのセッターです。サンプルマクロでは、標準モジュールから呼び出されたときに引数として”タッツー”がValに代入されています。受け取った値を変数の「Mポケモン_」に代入する処理をします。

なお、サンプルマクロでは変数であつかうデータが値であるため、プロシージャ名を「Property Let ポケモン名(ByVal Val As String)」としていますが、変数であつかうものがオブジェクトである場合は、「Property Let 〇〇〇」ではなく、「Property Set 〇〇〇」にする必要があります。

●Property Get ポケモン名() As String

プロパティプロシージャのゲッターです。サンプルマクロでは、標準モジュールから呼び出されると変数の「Mポケモン_」に入った値を戻り値としてポケモン名に代入する処理をします。

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

実行結果

イミディエイトウィンドウに以下が表示されます。

タッツー

このようにクラスモジュールで宣言したモジュールレベルの変数(プロパティ)は、プロパティプロシージャのセッターやゲッターを経由することで利用ができます。

クラスでパブリックレベルの宣言をした変数(プロパティ)のスコープ(適用範囲)

クラスモジュールの宣言セクションで「Public」ステートメントをつかって宣言します。

パブリックレベルの変数は、グローバル変数と呼ばれるものとなり、宣言をしたモジュール以外のモジュールからも値の代入や出力ができます。なお、パブリックレベルの変数の有効期限は、クラスモジュールを呼び出したモジュールの処理が完了するまでです。

それでは、クラスモジュールで宣言したパブリックレベルの変数に、標準モジュールから値の代入と出力を実行するマクロを見てみます。

クラスモジュールの宣言したパブリックレベルの変数のサンプルマクロ

同じモジュールで宣言した変数であっても、「Public」ステートメントをつかって宣言しているものは標準モジュールから値の代入や出力ができます。

以下のサンプルマクロでは、2つの標準モジュールと変数を宣言するクラスモジュールの3つを準備しています。それぞれの標準モジュールからクラスモジュールで宣言した変数に値を代入・出力してみます。

標準モジュール(クラス変数に値を代入して出力するA)
Sub クラス変数に値を代入して出力するA() '標準モジュールプロシージャA

    'クラスを使うための処理(クラス00)
    Dim クラス00 As ポケモンクラス
    Set クラス00 = New ポケモンクラス
    
    'クラスで宣言したパブリックレベル変数に値を代入する(クラス00)
    '変数を指定するときの書きかたは、クラス名.変数(プロパティ)名で指定する
    クラス00.C_G名前 = "アチャモ"
    クラス00.C_G高さ = 0.4
    クラス00.C_G重さ = 2.5
    
    'クラスで宣言したパブリックレベル変数に代入した値を出力する(クラス00)
    '変数を指定するときの書きかたは、クラス名.変数(プロパティ)名で指定する
    Debug.Print クラス00.C_G名前
    Debug.Print クラス00.C_G高さ
    Debug.Print クラス00.C_G重さ
    
    'クラスを使うための処理(クラス01)
    Dim クラス01 As ポケモンクラス
    Set クラス01 = New ポケモンクラス

    'クラスで宣言したパブリックレベル変数に値を代入する(クラス01)
    '変数を指定するときの書きかたは、クラス名.変数(プロパティ)名で指定する
    クラス01.C_G名前 = "イーブイ"
    クラス01.C_G高さ = 0.3
    クラス01.C_G重さ = 6.5
    
    'クラスで宣言したパブリックレベル変数に代入した値を出力する(クラス01)
    '変数を指定するときの書きかたは、クラス名.変数(プロパティ)名で指定する
    Debug.Print クラス01.C_G名前
    Debug.Print クラス01.C_G高さ
    Debug.Print クラス01.C_G重さ

End Sub

Sub クラス変数に値を代入して出力するB()'標準モジュールプロシージャB

    'クラスを使うための処理(クラス02)
    Dim クラス02 As ポケモンクラス
    Set クラス02 = New ポケモンクラス
    
    'クラスで宣言したパブリックレベル変数に値を代入する(クラス02)
    '変数を指定するときの書きかたは、クラス名.変数(プロパティ)名で指定する
    クラス02.C_G名前 = "フシギダネ"
    クラス02.C_G高さ = 0.7
    クラス02.C_G重さ = 6.9
    
    'クラスで宣言したパブリックレベル変数に代入した値を出力する(クラス02)
    '変数を指定するときの書きかたは、クラス名.変数(プロパティ)名で指定する
    Debug.Print クラス02.C_G名前
    Debug.Print クラス02.C_G高さ
    Debug.Print クラス02.C_G重さ

End Sub

こちらの標準モジュールでは、2つのプロシージャを作成していて、それぞれのプロシージャからクラスモジュール(ポケモンクラス)で宣言したパブリックレベルの変数に対して値の代入と出力をするマクロになります。

サンプルマクロの「クラス変数に値を代入して出力するA」では、「クラス00」と「クラス01」という名前のオブジェクトを2つ作成して値を代入・出力しています。また、同じモジュールにある別の「クラス変数に値を代入して出力するB」では、「クラス02」というオブジェクトを宣言して値の代入と出力をしています。

標準モジュール(クラス変数に値を代入して出力するB)
Option Explicit

Sub クラス変数に値を代入して出力するC() '標準モジュールプロシージャC

    'クラスを使うための処理(クラス02)
    Dim クラス03 As ポケモンクラス
    Set クラス03 = New ポケモンクラス
    
    'クラスで宣言したパブリックレベル変数に値を代入する(クラス02)
    '変数を指定するときの書きかたは、クラス名.変数(プロパティ)名で指定する
    クラス03.C_G名前 = "ポッチャマ"
    クラス03.C_G高さ = 0.4
    クラス03.C_G重さ = 5.2
    
    'クラスで宣言したパブリックレベル変数に代入した値を出力する(クラス02)
    '変数を指定するときの書きかたは、クラス名.変数(プロパティ)名で指定する
    Debug.Print クラス03.C_G名前
    Debug.Print クラス03.C_G高さ
    Debug.Print クラス03.C_G重さ

End Sub

もうひとつの標準モジュールになります。こちらもクラスモジュール(ポケモンクラス)で宣言したパブリックレベルの変数に対して値の代入と出力するマクロになります。こちらでは「クラス03」というオブジェクトに構成する要素として値を渡しています。

クラスモジュール(ポケモンクラス)
Option Explicit

'パブリックレベルの変数(プロパティ)の宣言
'変数名の「C_G」はクラスとグローバルの頭文字です。
Public C_G名前 As String
Public C_G高さ As Single
Public C_G重さ As Single

こちらがクラスモジュールになります。宣言セクションで「Public」ステートメントをつかって3つの変数(プロパティ)を宣言しています。

実行結果

イミディエイトウィンドウに以下を表示します。こちらは標準モジュール(クラス変数に値を代入して出力するA)の実行結果です。

先頭行の「アチャモ」から、6行目の「6.5」までが「クラス変数に値を代入して出力するA」の実行結果でそれ以降の行は「クラス変数に値を代入して出力するB」の結果となります。結果からいずれのプロシージャからもクラスで宣言した変数に値の代入と出力が出来ています。

アチャモ
0.4
2.5
イーブイ
0.3
6.5
フシギダネ
0.7
6.9

つづいて、標準モジュール(クラス変数に値を代入して出力するB)の実行結果です。イミディエイトウィンドウに以下を表示します。

ポッチャマ
0.4
5.2

異なるモジュールで宣言した変数であっても、Publicステートメントをつかって宣言しているため、標準モジュールから値の代入や出力ができます。

実行結果からもわかるとおり、クラスモジュールでも「Public」ステートメントをつかって宣言した変数は、パブリックレベルの変数(グローバル変数)となります。これらの変数(プロパティ)は、標準モジュールから値の代入や出力ができます。
サンプルマクロでも、標準モジュールAにある2つのプロシージャから変数の利用ができていることがわかります。また、下のイメージ図のように別の標準モジュールにあるプロシージャからでも問題なく変数(プロパティ)が利用できます。

クラスで宣言したパブリックレベルの変数のスコープ(適用範囲)のイメージ
エクセルVBAを基本から体系的に理解していきたいなら、おすすめの一冊。

VBAの変数におけるスコープ(適用範囲)まとめ

この記事では、標準モジュールやクラスモジュールで宣言した変数や定数で、スコープ(適用範囲)を指定したときに、他のモジュールやプロシージャから利用する方法を紹介しました。

スコープ(適用範囲)を意識してマクロを作れるようになると、大きな規模のマクロでも処理を分けて管理することができるため、可読性の向上とメンテナンスのしやすさなどの効果があります。

それでは、この記事で紹介した内容をまとめていきます。

  • 変数や定数は宣言方法によってスコープ(適用範囲)が指定できる
    宣言方法によってスコープ(適用範囲)が異なり、共有できる範囲が変わってくる
  • スコープ(適用範囲)は3つの種類がある
    • プロシージャレベル
      SubやFunctionなどのプロシージャのなかで「Dim」をつかって宣言します。宣言したプロシージャのなかで有効な変数です。
    • モジュールレベル
      モジュールの宣言セクションで「Private」をつかって宣言します。同じモジュールのプロシージャから利用ができるものとなり、宣言したモジュールのなかで有効な変数です。
    • パブリックレベル
      モジュールの宣言セクションで「Public」をつかって宣言します。プロジェクト全体で利用ができるものとなり、ほかのモジュールからでも値の代入や出力ができる変数です。
  • モジュールやパブリックレベルのスコープ(適用範囲)をあつかうときの注意点
    • 変数名の名づけかた
    • 共有している変数をほかのプロシージャで値を書きかえると意図しない動作が発生するリスクがある
    • 共有している変数を宣言したモジュールがわかるようにしておく
    • 共有している変数が影響するプロシージャをわかるようにしておく
  • 変数と同じく定数もスコープ(適用範囲)の指定ができる
    • 定数は宣言と同時に値を設定し、そのあとで値の変更はできない
    • 定数であつかえるデータ型は、変数と比較すると限られている
  • 変数の有効期限について
    • スコープ(適用範囲)によって変数の有効期限も変わる
    • プロシージャレベルの変数の有効期限は「Static」をつかえばブックを閉じるまで有効になる
    • 変数の有効期限を完全に信用するのではなく、値を長期的に保存する場合はワークシートのセルに書き出すなどの方法を検討する
  • 配列もスコープ(適用範囲)が指定できる
  • ユーザ定義型(構造体)はモジュールレベルとパブリックレベルでスコープ(適用範囲)が指定できる
  • クラスモジュールで宣言した変数にもスコープ(適用範囲)で指定できる
    • 一般的には小さなスコープ(適用範囲)で指定することが望ましいとされている
    • モジュールレベルの変数はプロパティプロシージャを経由することで値の代入・出力ができる

変数のスコープ(適用範囲)を理解すると今よりもっと便利に変数が使えるよ。

今回はここまで。

コダマのもりブログはにほんブログ村に登録しています。
ブログの記事が役に立ったと感じて頂けたら、フォローお願いいたします。

コメント

タイトルとURLをコピーしました