VBAで関数の引数に配列を使用する場合の指定の仕方とは?(パターンを全網羅してます)

29
あるま@ナメクジ情シス @aruma_jet

駆け出しです #助けてVBA 動的2次元配列の扱い vbaで配列を引数にすると、参照渡ししか出来ない!→バクの宝庫 variant型で定義すれば値渡しができる →抜け道的な話だけどメインに使って良き? 1次元配列はvariantで、 2次元配列は collectionとかdictionary使った方がいいのでしょうか…?

2021-06-26 00:21:12
あるま@ナメクジ情シス @aruma_jet

@yamaoka_ss 反応ありがとうございます!参考にさせていただきます。 宝庫は使い方調べた時出てきた1サイトの受けおりです… 別言語原にいた時は値渡しで加工していたので、直に参照するbyrefを私自身避けていたと思います 今回動的2次元配列の行拡張で、参考サイトではbyval variantを使っていたので疑問でした

2021-06-26 01:57:47
ほえほえ@スプシマン @hoehoe1234

@aruma_jet @yamaoka_ss こんばんは。結論だけ申しますと配列を引数として渡す場合の呼ばれる関数の仮引数は「参照型バリアント仮引数(例 byref a_arr)」が適切だと思います。これを理解するにはVBAの配列モデルを理解する必要があります。しかし、一旦この話はおいておいて、仮引数の検討だけしてみます。

2021-06-26 02:35:13
ほえほえ@スプシマン @hoehoe1234

@aruma_jet @yamaoka_ss 以下、「具象型」とはdim arr() as Longなどの型を指定した動的配列とします。 ①仮引数がbyValの具象型⇒構文エラー ②仮引数がbyRefの具象型⇒いわゆる参照の配列渡し。参照渡しは「同じ変数」となるので厳密な型の一致が必要。上記配列の場合はIntegerの配列も渡せなく、不便です。

2021-06-26 02:38:46
ほえほえ@スプシマン @hoehoe1234

@aruma_jet @yamaoka_ss ③仮引数がbyValのバリアント型⇒実引数が具象型配列、またはバリアントバインド配列であっても配列がコピーされます。 ④仮引数がbyRefのバリアント型⇒実引数が具象型であれば仮引数は具象型配列へのポインタとなります。実引数がバリアントバインド配列であれば「同じ変数」となります。

2021-06-26 02:41:13
ほえほえ@スプシマン @hoehoe1234

@aruma_jet @yamaoka_ss このあたりの説明は、①VBAにおける配列のモデルと、②バリアント型の挙動と、③実引数ー仮引数の仕組みの3つを理解しないと正確な動きは理解できません。ですから一旦は仮引数はバリアント型仮引数としておけばすべてのケースに対応できるのでこの方式を推奨しています。

2021-06-26 02:42:57
ほえほえ@スプシマン @hoehoe1234

@aruma_jet @yamaoka_ss 懸念されている「バグの温床」ですが、もし引数の配列を変更したくないのであれば、関数の頭で配列をコピーしてしまえば問題ありません。また、これはむしろ、引数の配列の要素を書き換えられるということで利点のほうが大きいです。具体的な記法が必要であればその旨おしらせください。

2021-06-26 02:45:05
ほえほえ@スプシマン @hoehoe1234

@aruma_jet @yamaoka_ss まとめ。 配列を引数に取る関数は図の4つのパターン(実際は3つ)がありますが、メリット/デメリットとから2つめ、または4つめを推奨します。4つめは配列のコピーが発生することにご注意ください。 pic.twitter.com/pcwYabNgrM

2021-06-26 02:47:59
拡大
ほえほえ@スプシマン @hoehoe1234

配列を関数の引数に使うときにどのように振る舞うかは、①配列のモデル、②バリアントの挙動、③引数が渡される仕組み(特に参照型)の3つを正しく理解しないといけないので、VBAでもわかりにくいもののひとつではないでしょうか?

2021-06-26 02:52:53
ほえほえ@スプシマン @hoehoe1234

検証ソースコードと検証結果だけ貼り付けておきますので、配列にご興味のあるかたは解析してみてください。使用している関数は滝Libにふくまれています。 まずは検証コード(呼び出し側)です。 pic.twitter.com/uQcrYRVGfW

2021-06-26 02:54:23
拡大
ほえほえ@スプシマン @hoehoe1234

呼び出される関数はこちらになります。4パターンありますが3つめはエラーとなるので、実際は3パターンになります。 pic.twitter.com/psrl9dCEEb

2021-06-26 02:55:32
拡大
ほえほえ@スプシマン @hoehoe1234

イミデトウインドウへの出力です。最初は呼び出し側でダンプします。 pic.twitter.com/7GTHcRxkSa

2021-06-26 02:57:17
拡大
ほえほえ@スプシマン @hoehoe1234

呼び出し①~④の結果です。メモリ上の配列(SAFEARRAYといいます)のアドレスと配列変数の中身を追っていくとアドレスがいろいろと合致しているのが分かると思います。 pic.twitter.com/5xKIOk2Zfl

2021-06-26 02:59:27
拡大
ほえほえ@スプシマン @hoehoe1234

呼び出し⑥~⑦の結果です。こちらは配列がコピーされているのが分かると思います。これで配列を引数に渡す場合の全パターンを網羅していますのでご興味のある方は絵を書くなどして解析してみてください。一度解析すれば多くのことが分かると思います。 おしまい。 pic.twitter.com/9JKfPXp3em

2021-06-26 03:01:46
拡大
あるま@ナメクジ情シス @aruma_jet

@hoehoe1234 @yamaoka_ss ご丁寧にありがとうございました!非常に分かりやすいです! 実引数longのbyref longは1対1、 sub共通化時にはbyref varで受け口広げ、各実引数直に弄ると良き 実引数変更✖ →byval var sub共通化時に弄り配列をメインで使う場合は戻り値を… 参考サイトは後者戻り値系でした!多分理解できました!

2021-06-26 03:47:42
ほえほえ@スプシマン @hoehoe1234

@aruma_jet @yamaoka_ss こんばんは。このあたりは設計の範疇だと思うのですが、配列を書き換える場合、 ①仮引数で受け取った配列の要素を直接変更⇒いわゆる「インプレース処理」 ②受け取った配列の要素数が変わるような場合は関数の戻り値にする ③要素数は変わらなくても「写像」の観点があれば関数の戻り値にする

2021-06-26 03:53:41
ほえほえ@スプシマン @hoehoe1234

@aruma_jet @yamaoka_ss などがあり、どれが良いとか悪いとかではなくて、目的と設計に応じて使いわける必要があると思います。 参照型仮引数は「同じ変数」ですので、配列を多段階の関数で引き回す場合には各関数でコピーが発生せず、特に便利に使えます。

2021-06-26 03:56:02