[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]
『ParamArrayに値・配列を渡した時の処理』(にゅるん)
お世話になっております。
VBAでは、ParamArrayに対して配列を指定した時、ジャグ配列になってしまうようです。
そこで下記のコードのようにhoge(0)の型を見ることで、配列かどうかを判別してから処理を分ける案を考えました。
Sub TestFunc1() Call Func1(1, "a", "b", "c") Call Func1(1, Array("x", "y", "z")) End Sub
Sub Func1(i As Long, ParamArray hoge())
If TypeName(hoge(0)) = "Variant()" Then Debug.Print hoge(0)(i) If UBound(hoge(0)) > i Then Call Func1(i + 1, hoge(0)) Else Debug.Print hoge(i) If UBound(hoge) > i Then Call Func1(i + 1, hoge) End If End Sub
しかし1行ならともかく、同じような処理のコードを2種類書くことになるため
共通処理をプロシャージャFunc2に抽出して処理を共通化することを考えました。
ところが、コンパイルエラー「ParamArrayの使い方が適切ではありません。」が出てしまいます。
Sub TestFunc1kai() Call Func1kai(0, "a", "b", "c") Call Func1kai(0, Array("x", "y", "z")) End Sub
Sub Func1kai(i As Long, ParamArray hoge())
If TypeName(hoge(0)) = "Variant()" Then Call Func2(i, hoge()) If UBound(hoge(0)) > i Then Call Func1(i + 1, hoge(0)) Else Call Func2(i, hoge) If UBound(hoge) > i Then Call Func1(i + 1, hoge) End If End Sub
Sub Func2(i As Long, hoge As Variant)
Debug.Print hoge(i) End Sub
このような場合、どのように書けば良いのでしょうか。
それともどちらの入力も受け付けられる仕様は望ましくないのですか?
(C#では普通に書いて使っていたと思うのですが・・・)
< 使用 Excel:Excel2016、使用 OS:Windows10 >
Sub TestFunc1() Call Func1(1, "a", "b", "c") Call Func1(1, Array("x", "y", "z")) End Sub
Sub Func1(iw As Long, cw1 As Variant, Optional cw2 As String, Optional cw3 As String) Dim i As Long
If TypeName(cw1) = "Variant()" Then For i = 0 To UBound(cw1) Debug.Print cw1(i) Next i Else Debug.Print cw1, cw2, cw3 End If End Sub (???) 2018/08/16(木) 16:02
ネット検索すると、こんなのありました。 ↓ https://msdn.microsoft.com/ja-jp/vba/language-reference-vba/articles/invalid-paramarray-use ParamArray の使い方が正しくありません 配列または ByRef Variant を想定している別のプロシージャに ParamArray が 引数として渡されました。 ParamArray パラメーターをバリアント型 ( Variant ) に割り当てて、バリアントを渡します
まぁ、これだけで全てが解決する訳じゃなさそうですけど、 本当にやりたいことが分からないので・・
(半平太) 2018/08/16(木) 16:18
でももう少し自然な形で書く方法は無いものでしょうか。
一応補足しておくと引数の数は決して3つ程度ということはありません。
いくつの場合でも対応出来るような書き方が知りたいです。
半平太様
>本当にやりたいことが分からないので・・
具体的に何をしたいという訳ではありません。
このような多数のデータを受け取る関数を作るときの技法が知りたいのです。
(上記ではコード量を減らすためにあえて再帰させていますが)
解説ページの意図が分かりにくいですね・・ちょっとジックリ読んでみます。
(にゅるん) 2018/08/16(木) 16:28
しかし、配列を使った指定のみ可能にすれば、配列の要素数が変わるだけで、引数は1個で済みますよ。 引数の個数を可変にするのを止めるだけかと思います。(汎用性が高い = 保守性が低い、不具合発生率が高い、作成時間がかかる、となるので、技術者はシンプルイズベストを目指すものと思っています)
(???) 2018/08/16(木) 16:54
※ VBAの配列はすべて「SAFEARRAY」です。対するC#などの配列はCArray。 なので扱いが全く異なります。
まず、SAFEARRAYの原理を理解することだと思います。 下記はテスト材料としてのソースです。
Declare PtrSafe Sub MoveMemory Lib "Kernel32" Alias "RtlMoveMemory" _ (ByRef Destination As Any, _ ByRef Source As Any, _ Optional ByVal Length As Long = 4&)
Sub TestFunc1kai() Call Func1kai(1, "a", "b", "c") Call Func1kai(1, Array("x", "y", "z"))
End Sub
Sub Func1kai(ByVal i As Long, ParamArray hoge()) Dim v() As Variant, bnd As Long Dim ppsa As LongPtr, psa As LongPtr
bnd = UBound(hoge) If bnd = -1 Then Exit Sub
If (VarType(hoge(0)) And vbArray) = 0 Then MoveMemory ppsa, ByVal VarPtr(i) + 4 MoveMemory psa, ByVal ppsa
MoveMemory ByVal VarPtr(bnd) + 4, psa MoveMemory ByVal ppsa, 0& 'deref hoge()
Else v() = hoge(0)
End If
'VARIANT型配列を作業用として扱う Call Func2(i, v())
End Sub
Sub Func2(i As Long, hoge() As Variant) Debug.Print hoge(i) End Sub (Abyss2) 2018/08/16(木) 16:58
当初旨く行ったコード Sub Func1(i As Long, ParamArray hoge()) If TypeName(hoge(0)) = "Variant()" Then Debug.Print hoge(0)(i) ←--------------------------------- チャンと場合分けしているので添え字が書かれている If UBound(hoge(0)) > i Then Call Func1(i + 1, hoge(0)) Else Debug.Print hoge(i) ←--------------------------------- チャンと場合分けしているので添え字は無い If UBound(hoge) > i Then Call Func1(i + 1, hoge) End If End Sub
トラブった(と思われる)コード Sub Func2(i As Long, hoge As Variant) Debug.Print hoge(i) ←---------------------------------場合分けがない。 End Sub
そうなると
Sub Func1kai(i As Long, ParamArray hoge()) If TypeName(hoge(0)) = "Variant()" Then Call Func2(hoge(0)(i)) ←--------------------------------- チャンと場合分けする If UBound(hoge(0)) > i Then Call Func1kai(i + 1, hoge(0)) Else Call Func2(hoge(i)) ←--------------------------------- チャンと場合分けする If UBound(hoge) > i Then Call Func1kai(i + 1, hoge) End If End Sub
Sub Func2(hoge As Variant) Debug.Print hoge ←---------------------------------シンプルに処理する。 End Sub
(半平太) 2018/08/16(木) 17:12
???様
>シンプルイズベストを目指すものと思っています
仰る通りだと思います。
今回はC#で慣れた書き方なので、考え方自体は(私にとっては)シンプルなつもりです。
あまり複雑化しそうなら止めようと思います。
Abyss2様
>※VBAの配列はすべて「SAFEARRAY」です。対するC#などの配列はCArray。
> なので扱いが全く異なります。
プログラム言語の根幹の部分の話ですね。
メモリを直接操作しているのでしょうか。
私は手を出さないほうが良さそうです。
半平太様
上記エラーが出たのは、Func1kaiの「Call Func2(i, hoge)」などの部分です。
メインの処理で配列全体を使わない場合は、提案頂いた方法にするのが良さそうですね。
(にゅるん) 2018/08/16(木) 17:34
要するに一旦Variant変数にデータをコピーしてしまえば良いようです。
Sub TestFunc1kai() Call Func1kai(0, "a", "b", "c") Call Func1kai(0, Array("x", "y", "z")) Call Func1kai(0, Split("d,e,f", ",")) End Sub
Sub Func1kai(i As Long, ParamArray hogehoge()) Dim hoge As Variant If (VarType(hogehoge(0)) And 8192) = 8192 Then hoge = hogehoge(0) Else hoge = hogehoge End If '-----処理 Debug.Print hoge(i) '----- End Sub たったこれだけで全てが解決しました・・・。 繰り返しますが、皆様本当にありがとうございました。 (にゅるん) 2018/08/16(木) 17:40
変数代入せずとも、以下のようにも書けますね。「処理」部分を1つだけ書くか、同じものを2つ書くかの違いが出ますが、代入を1回減らす効果があるかと思います。
Sub Func1kai(i As Long, ParamArray hogehoge()) Dim n As Long
If VarType(hogehoge(0)) And vbArray Then For n = 0 To UBound(hogehoge(0)) Debug.Print hogehoge(0)(n); Next n Else For n = 0 To UBound(hogehoge) Debug.Print hogehoge(n); Next n End If Debug.Print End Sub (???) 2018/08/17(金) 09:29
Sub Func1kai(ParamArray PA()) Dim i As Long Dim j As Long
For i = 0 To UBound(PA) If VarType(PA(i)) And vbArray Then For j = 0 To UBound(PA(i)) Debug.Print PA(i)(j); Next j Else Debug.Print PA(i); End If Next i Debug.Print End Sub (???) 2018/08/17(金) 09:48
>変数代入せずとも、以下のようにも書けますね。「処理」部分を1つだけ書くか、
>同じものを2つ書くかの違いが出ますが、代入を1回減らす効果があるかと思います。
そうです。でも同じ処理を2つ書くのは元々出来ていたんですよ(笑)
>いっそ、引数を1つにまとめても良い訳ですね。 面白い使い方ですが、有効な使いどころは思いつかず…。
目的の異なる全ての引数を1つにまとめれば良いというものでもないと思いますが・・?
ParamArrayが有効な場面は別に珍しくないです。
カスタム版のSum、Min、Maxを作るときとか。
あとずっと違和感があったのですが、ふと気が付きました。
>If (VarType(hogehoge(0)) And 8192) = 8192 Then
>If VarType(hogehoge(0)) And vbArray Then
これは
If IsArray(hogehoge(0)) Then
で十分じゃないかと。
(にゅるん) 2018/08/17(金) 10:00
[ 一覧(最新更新順) ]
YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki.
Modified by kazu.