[[20221111174405]] 『グローバル変数を使わずに変数の受け渡しをする方』(シロ) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『グローバル変数を使わずに変数の受け渡しをする方法』(シロ)

お世話になります。
マクロ初心者で趣味でマクロを作成しております。

以下の条件でマクロを作成しています。
・10万文字のテキストデータ作成してそれを元に文字列を作成するマクロ
・テキストデータ作成と文字列作成はモジュール及びプロシージャを分割して処理
・テキストデータの受け渡しにPublic変数を使用

ご教授願いたいのはPublic変数(グローバル変数)についてです。

インターネットでマクロに関して調べていると、グローバル変数は使ってはいけない、グローバル変数はデータが予期なく消失することがある、などと書いてあり、確かに、グローバル変数はデータの視認性、堅牢性、エラーや修正時のメンテナンス性が非常に悪いので、思い切ってグローバル変数無しに修正しようと思いました。

インターネットによく書いてあるグローバル変数の対策として
1.引数で受け渡し
2.ワークシートを使う
3.モジュール変数なら最悪OK
4.クラスで保持する
がありました。
1〜3は検証済です。
問題は4.の『クラスで保持する』についてです。
ネットで調べても具体的なものがなかったのでテストマクロを作って検証しました

以下テストマクロ

'>>>>>>>>>>>>>>>>>>>>>>>
'★★★Private変数、Dim変数 だけでプロシージャ間、モジュール間で文字列『ABC』を受け渡すマクロ
'★★ クラスモジュールのPropertyを使って受け渡してDebugPrintで3回表示  
'>>>>>>>>>>>>>>>>>>>>>>>>

'★標準モジュール、メイン処理
Sub クラスのGetで変数読み出し()

    '文字列『ABC』をDebugPrintで3回表示
    Dim new_abc As String
    new_abc = "ABC"
    Dim cls_変数 As cls_変数保持: Set cls_変数 = New cls_変数保持
    cls_変数.書き込み_abc = "ABC"
    Debug.Print cls_変数.呼び出し_abc  
    Call DebugPrint_2回目    
    Call クラスモジュール_別プロシージャ_DebugPrint_3回目  
End Sub

'★メインと同じモジュール、別プロシージャ
Sub DebugPrint_2回目()

    Dim cls_変数 As cls_変数保持: Set cls_変数 = New cls_変数保持
    Debug.Print cls_変数.呼び出し_abc
End Sub

'★別モジュール、DebugPrint3回目用
Sub クラスモジュール_別プロシージャ_DebugPrint_3回目()

    Dim cls_変数 As cls_変数保持: Set cls_変数 = New cls_変数保持
    Debug.Print cls_変数.呼び出し_abc
End Sub

'★クラスモジュール
Private 値保持abc As String

Property Get 呼び出し_abc() As String

    呼び出し_abc = 値保持abc
End Property

Property Let 書き込み_abc(new_abc As String)

    値保持abc = new_abc
End Property

<DebugPrint表示結果>
1回目 『ABC』表示
2回目 表示なし
3回目 表示なし
1回目は『ABC』が表示できたが、2回目、3回目は『ABC』表示せず

次に、標準モジュールで作成したPropertyプロシージャを標準モジュールで作成

'>>>>>>>>>>>>>>>>>>>>>>>
'★★★Private変数、Dim変数 だけでプロシージャ間、モジュール間で文字列『ABC』を受け渡すマクロ
'★★ 標準モジュールのPropertyを使って受け渡してDebugPrintで3回表示  
'>>>>>>>>>>>>>>>>>>>>>>>>

'★標準モジュール、メイン処理
Sub 標準モジュールのGetで読み出し()

    '文字列『ABC』DebugPrintで3回表示
    Dim new_abc As String
    new_abc = "ABC"
    書き込み_abc = new_abc
    Debug.Print 呼び出し_abc  '1回目
    Call 標準モジュール_DebugPrint_2回目    'OK  『ABC』表示
    Call 標準モジュール_DebugPrint_3回目_別モジュール    'OK  『ABC』表示
End Sub

'★メインと同じモジュール、別プロシージャ
Sub 標準モジュール_DebugPrint_2回目()

    Debug.Print 呼び出し_abc()   '2回目
End Sub

'★別モジュール、DebugPrint3回目用
Sub 標準モジュール_DebugPrint_3回目_別モジュール()

    Debug.Print 呼び出し_abc()   '3回目
End Sub

'★標準モジュール、Property
Private 値保持abc As String

Property Get 呼び出し_abc() As String

    呼び出し_abc = 値保持abc
End Property

Property Let 書き込み_abc(new_abc As String)

    値保持abc = new_abc
End Property

<DebugPrint表示結果>
1回目 『ABC』表示
2回目 『ABC』表示
3回目 『ABC』表示
標準モジュールのPropertyGet は別プロシージャ、別モジュールでも『ABC』が表示された

テストの結果をふまえて、教えていただきたいことが2つあります

1.標準モジュールのProperty処理について
 Dim変数なのにEndSubでも値が保持され続ける理屈を教えて頂きたいです。
 保持している変数の有効期間もおしえていただければ助かります。

2.クラスモジュールのProperty処理について
 クラスでグローバル変数のように変数に値を保持する方法がわかりません。
 よろしければ、具体的なコードをお教え願えませんでしょうか?

よろしくお願いします

< 使用 Excel:Excel2013、使用 OS:Windows10 >


 > Dim変数なのにEndSubでも値が保持され
  その様な状況は生じていない様に見受けられます。

    '★標準モジュール、Property
    Private 値保持abc As String              ←コイツでしょ。保持してるのは。
    Property Get 呼び出し_abc() As String
        呼び出し_abc = 値保持abc
    End Property
    Property Let 書き込み_abc(new_abc As String)
        値保持abc = new_abc
    End Property

 > クラスでグローバル変数のように変数に値を保持する方法
  たぶん現状で大丈夫だと思います。

    '★クラスモジュール
    Private 値保持abc As String              ←コイツが保持してくれるでしょう。
    Property Get 呼び出し_abc() As String
        呼び出し_abc = 値保持abc
    End Property
    Property Let 書き込み_abc(new_abc As String)
        値保持abc = new_abc
    End Property

 クラスモジュールの実験で
 >1回目は『ABC』が表示できたが、2回目、3回目は『ABC』表示せず
 となったのは、単にインスタンスがそれぞれ別だからでしょう。

 1回目
    Dim cls_変数 As cls_変数保持: Set cls_変数 = New cls_変数保持
    cls_変数.書き込み_abc = "ABC"                                   ←の処理があるのは「1回目」だけ
 2回目
    Dim cls_変数 As cls_変数保持: Set cls_変数 = New cls_変数保持
 3回目
    Dim cls_変数 As cls_変数保持: Set cls_変数 = New cls_変数保持

(白茶) 2022/11/11(金) 18:56:49


白茶さん、ご返信ありがとうございます。

ご指摘の通りです。

    '★標準モジュール、Property
    Private 値保持abc As String              ←コイツでしょ。保持してるのは。

ウオッチで『値保持abc』を見ているとメインの処理がEndSubしても値を保持しており、マクロの停止ボタンを押すとようやく値が『""』になります。
『値保持abc』はモジュール変数なので自身のモジュール内の処理がEndSubし、他のプロシージャに移っても値保持されるのと、メイン処理がEndSubしても値保持されるのが不思議でした。
Propertyプロシージャ、メインプロシージャのどちらがEndSubになっても、『値保持abc』が値保持しているという意味でした。

クラスについてもご指摘のとおりです。最初の投稿でも書きましたが、グローバル変数の代わりに、クラスで変数値を保持するのというのがよくネットに書かれていますが、クラスはインスタンスの宣言でNewをSetした時点で新規になると思っているので、他のプロシージャに回せないのはとてもグローバル変数とは思えなかったのです。クラスで他のプロシージャにも使い回せるグローバル変数のようなものは無理なのでしょうか。
(シロ) 2022/11/11(金) 20:07:03


 >メイン処理がEndSubしても値保持される
 文字通りとも言えますが、その「Subプロシージャが終了した」だけなんですね。
 モジュールレベルで宣言されたPrivateとかPublic変数は、
 同じ様に表現するならば「モジュールが終了」するまでは値が保持される。って感じです。

 「モジュールの終了」って言ったら「ファイルを閉じるまで」って言ってる様なものです。
 ・・・と考えてみたらしっくり来ません? (来ないか ^^;)

 >他のプロシージャに回せないのはとてもグローバル変数とは思えなかった
 これはあくまで私見ですけど、
 「グローバル変数」という表現は「標準モジュール」でしか成り立たないと思ってます。(考え方として)
 実際、Globalキーワードで変数を宣言(昔の宣言方法らしいです)出来るのは標準モジュールだけです。
 クラスモジュールやオブジェクトモジュールでは使えません。

 Publicキーワードはあくまで「外に見せる」的な意味合いであって、「共有」という意味合いではないと思ってます。
 「共有」という機能は「標準モジュール」が持ってる特性です。(暗黙的にSharedなどと言うらしいですね)

 従って
 >クラスで他のプロシージャにも使い回せるグローバル変数のようなものは無理
 については、
 無理というか、そもそもそういう風に使うものではないんじゃないのかな?  と思うのです。
 (その為に標準モジュールってものがあるんだなー、と思っておけば良くないですか?)

(白茶) 2022/11/11(金) 20:37:47


 > クラスで他のプロシージャにも使い回せるグローバル変数のようなものは無理なのでしょうか。

 他の変数と同じように、標準モジュールでPublic宣言すれば、グローバル変数として他のモジュールからでも参照できます。

 '★標準モジュール
 Option Explicit
 Public cls_変数 As New cls_変数保持

 '★標準モジュール、メイン処理
 Sub クラスの変数読み出し()
    '文字列『ABC』をDebugPrintで3回表示
    cls_変数.書き込み_abc = "ABC"
    Debug.Print cls_変数.呼び出し_abc
    Call DebugPrint_2回目
    Call クラスモジュール_別プロシージャ_DebugPrint_3回目
 End Sub

 '★メインと同じモジュール、別プロシージャ
 Sub DebugPrint_2回目()
    Debug.Print cls_変数.呼び出し_abc
 End Sub

 '★別モジュール、DebugPrint3回目用
 Sub クラスモジュール_別プロシージャ_DebugPrint_3回目()
    Debug.Print cls_変数.呼び出し_abc
 End Sub

 <DebugPrint表示結果>
 1回目 『ABC』表示
 2回目 『ABC』表示
 3回目 『ABC』表示

 このクラスはブックが開いている間は有効です。
 ただ、それゆえに通常のグローバル変数と同じ問題が生じます。

 グローバル変数は、ブックが開いている間は、どこからでも参照できて、変更できるという
 自由度の高さから、便利ではあるが、バグの原因になったり、メンテナンス性の問題が発生します。

 このような自由度を制限して、堅牢性、メンテナンス性をあげようというのがクラスを使う目的の一つだと思います。
 有効期限、参照範囲をプログラマーがコントロールすることがクラスなら可能なのです。

 例えば、上記の処理なら、下記のようにして有効期限を限定できます。
 別モジュールには引数でクラスのインスタンスを渡します。

 '★標準モジュール、メイン処理
 Sub クラスのGetで変数読み出し()
    Dim cls_変数 As cls_変数保持

    Set cls_変数 = New cls_変数保持 'ここから有効期限始まり
        '文字列『ABC』をDebugPrintで3回表示
    cls_変数.書き込み_abc = "ABC"
    Debug.Print cls_変数.呼び出し_abc
    Call DebugPrint_2回目(cls_変数)
    Call クラスモジュール_別プロシージャ_DebugPrint_3回目(cls_変数)

    Set cls_変数 = Nothing 'ここで、cls_変数を破棄、有効期限終わり

    Debug.Print cls_変数.呼び出し_abc 'エラーになる
 End Sub
 '★メインと同じモジュール、別プロシージャ
 Sub DebugPrint_2回目(cls_変数 As cls_変数保持)
    Debug.Print cls_変数.呼び出し_abc
 End Sub

 '★別モジュール、DebugPrint3回目用
 Sub クラスモジュール_別プロシージャ_DebugPrint_3回目(cls_変数 As cls_変数保持)
    Debug.Print cls_変数.呼び出し_abc
 End Sub

 上記のメイン処理は下記のようにWithを使って記述することもできます。
 これは有効期限が明確です。
 (RangeやSheetのオブジェクトを扱うときによくみる記述法ですね。)

 '★標準モジュール、メイン処理
 Sub クラスのGetで変数読み出し()

    With New cls_変数保持 'ここから有効期限始まり
        '文字列『ABC』をDebugPrintで3回表示
        .書き込み_abc = "ABC"
        Debug.Print .呼び出し_abc
        Call DebugPrint_2回目(.Self)
        Call クラスモジュール_別プロシージャ_DebugPrint_3回目(.Self)
    End With  'インスタンスを破棄、有効期限終わり

 End Sub

 '★クラスモジュール cls_変数保持
 Option Explicit
 Private 値保持abc As String

 Property Get 呼び出し_abc() As String
    呼び出し_abc = 値保持abc
 End Property
 Property Let 書き込み_abc(new_abc As String)
    値保持abc = new_abc
 End Property
 '自分自身を返すプロパティを宣言しておく
 Property Get Self() As cls_変数保持
    Set Self = Me
 End Property

(hatena) 2022/11/11(金) 22:24:31


 途中参加ですみません
 興味深い内容だったので眺めさせてもらってます。
 差し支えなければ
 >グローバル変数の代わりに、クラスで変数値を保持するのというのがよくネットに書かれていますが
 こちらの部分、参考URLございますか?
 自分自身結構勉強してきたつもりなのですが、そんな表現あったかなと、、、

 自分が見た中では値の保持については、シートに書き込む、誰も見なさそうなブックのプロパティに書き込むとか、
 txtに出力してしまう、ループ処理の中でdoeventsを挟んでプロシジャーを動かしたまま
 別のプロシジャーを実行するとかがあったと思います。
 そもそも値保持しておいて後で使う状況があまりなかったのですが、よくある事ですか?
 ビンゴゲームように値保持考えた時は、シートに書き出してました。

(稲葉) 2022/11/11(金) 23:30:13


 私も改めて冒頭に戻ってみて追記したくなりました。^^;

 > 1.引数で受け渡し
 > 2.ワークシートを使う
 > 3.モジュール変数なら最悪OK
 > 4.クラスで保持する

 の内、そもそも4番は「グローバル変数の対策」という当初の議題において
 他の3つと同列に扱うものではなかったんじゃないかな? と。

 仮にテキストデータを「クラスで保持」したところで、
 そいつ(クラス?)を「引数で受け渡し」するか「モジュール変数」で扱うか
                                                           ...って事ですからね結局。

 それを踏まえると、仰るところの
 >クラスで他のプロシージャにも使い回せるグローバル変数のようなもの
 ってのは、
 たぶんニュアンス的に「静的クラス」の様なもの作れないのか? って感じだったのかと思います。
 いわゆるシングルトン。Newでインスタンス化できない(する必要のない)クラス。
 「標準モジュールもどき」が作りたい。みたいな。

 ただ、やっぱ
 「じゃそれ普通にグローバル変数でよくない?」って事になっちゃうんですよね^^;
 仮に静的クラスが作れたとしても、
 標準モジュールのPublic変数の様にSharedな振る舞いは無理だろうし。
 ならいっそ
 ThisWorkbookモジュールにPublic変数で保持して貰って受け渡し
 でも同じ事だしねぇ...

 てなわけで、当初の議題に対しては、
 > 1.引数で受け渡し
 > 2.ワークシートを使う
 > 3.モジュール変数なら最悪OK
 の検証が出来てるから、もうそれでいいんじゃないかと、個人的には思いました。

(白茶) 2022/11/11(金) 23:39:46


 そもそも、

 > グローバル変数はデータの視認性、堅牢性、エラーや修正時のメンテナンス性が非常に悪いので、
 > 思い切ってグローバル変数無しに修正しようと思いました。

 と、

 > クラスで他のプロシージャにも使い回せるグローバル変数のようなものは無理なのでしょうか。

 は、矛盾してますよね。
 クラスの変数だろうが、通常の変数だろうが、グローバル変数のデメリットは同じですからね。
(hatena) 2022/11/11(金) 23:44:46

白茶さん、hatenaさん、稲葉さん、 ご返事ありがとうございます。

稲葉さんのご返信
>グローバル変数の代わりに、クラスで変数値を保持する
ですが、私が直接見たサイトのURLは失念しておりますが、グローバル変数のデメリットをインターネットで調べている時に対策としてクラスで保持するというのがいくつかあったのでその表現になりました。直接のURLが明示できず回答にならなくてすみません。

私がグローバル変数をクラスで代替しようと思ったのは、クラスにテキストデータを保持させておき、必要時にPropertyGetで値を取り出すことができれば、コードミスで値が変わってしまっていることも無いし、Dim変数としてプロシージャ内で呼び出すのでエラーもみつけやすいのではないかと思ったのがキッカケでした。

皆様、貴重な知識とご意見を頂きまことに感謝いたします。
コードの表示本当に助かりました。
大変勉強になりました。
本当にありがとうございました。

(シロ) 2022/11/12(土) 04:10:46


 本人が収めてしまっているので蛇足ですが
 >必要時にPropertyGetで値を取り出す
 >コードミスで値が変わってしまっていることも無い
 >Dim変数としてプロシージャ内で呼び出す
 クラスの利点として挙げられている3つとも、NOだと思うのですが、
 こちらは既にご理解なさっています?

 hatenaさんの2022/11/11(金) 22:24:31投稿の通り
 クラスにしても別プロシジャーに、渡す場合もシロさんの挙げられた
 1,3に該当する方法でしか渡せませんので、どちらにしろ結合度は強くなりメンテナンス性は落ちるかなと。
 (なので、必要がなければプロシジャーもモジュールもわけない方が良いのでは)

 最近のスレッドだと、フォームと標準モジュールの値受け渡しで白茶さんの投稿が参考になりました!
 標準モジュールは特殊な場所で、自分に必要なペンは自分で準備しろってところ。
[[20210611165547]] 『Public変数について』(しのみや) >>BOT

 今回の件も検討続けられるのでしたら、ユーザーフォームをmodelessで起動して
 ユーザーフォーム内で値の橋渡ししてあげれば、値の有効期限間も視認できるので便利かなと。
 ただ、ユーザーフォームも複数インスタンスが生成できるので、結局はモジュールレベル変数に
 入れてあげないといけないのかな、、?
 ここは検証不足です。あとで自分で試してみます。
https://thom.hateblo.jp/entry/2017/01/17/204151

 パソコン触れるようになったら検証結果載せますね
(稲葉) 2022/11/12(土) 06:36:51

 論点に一緒くた感が強いのですけど・・
  変数のデータが予期なく消失することがあることが問題なのか
  データの視認性、堅牢性、エラーや修正時のメンテナンス性が非常に悪いのが問題なのか
  想定するPUBLIC変数は、オブジェクト変数なのか、NONオブジェクト変数(文字、値)なのか?

 行  _________A_________  ________B________  _______C_______  ___D___  _______E_______
  1                       オブジェクト変数   同左(Private)   値変数   同左(Private) 
  2  予期なく消失                                                                     
  3  低いメンテナンス性                                                               

  上のテーブルで下の対策がどこに入る(と思う)のですか?
   1.引数で受け渡し
   2.ワークシートを使う
   3.モジュール変数なら最悪OK
   4.クラスで保持する

 ※上のテーブルが適切でなければ修正してください。兎に角、議論の的を絞りたい。

(半平太) 2022/11/12(土) 09:58:55


 流れぶった切ってしまって済みませんが、↑↑の投稿の検証できたのでアップです。
 UserForm1に入れるコード
    Option Explicit
    Public TEST変数 As String

 Module1に入れるコード
    Option Explicit
    Sub 標準モジュール1のマクロ()
        UserForm1.TEST変数 = "テスト"
        UserForm1.Show vbModeless
    '    UserForm1.Hide
        MsgBox "モジュール1のテスト終了時:" & UserForm1.TEST変数
    End Sub

 Module2に入れるコード
    Option Explicit
    Sub 標準モジュール2()
        If UserForm1.TEST変数 = "" Then
            MsgBox "UserForm1は実行されていません"
        Else
            MsgBox UserForm1.TEST変数
        End If
    End Sub

 グローバル変数を使わずに(UserFormのオブジェクト変数を含む)
 UserFormをグローバル変数の変わりに使うことで、異なるモジュール間で値の受け渡しができました。
 ただし、再度Showする・Hideせずに×で閉じるなどで初期化されてしまいますので、堅牢性には劣るかなと・・・。

 やはりエクセルならシート上にデータを残す方法が一番良い気がしますー。

(稲葉) 2022/11/13(日) 20:46:20


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.