|
2017/02/27(Mon) 21:46:41 編集(投稿者)
■No83019 (ぼんかば さん) に返信 > VBAでは > Dim aaa(5) as single > bbb = aaa
それができるのは、VBA6 以降に限られますね。
VBA5 以前では ReDim bbb(5) For n = 0 To 5 If IsObject(aaa(n)) Then Set bbb(n) = aaa(n) Else Let bbb(n) = aaa(n) End If Next のように処理する必要があったと思います。(今回、If は冗長ですが)
で、上記で Set ステートメントと Let ステートメントを明示的に記述していますが、 VBA の場合、参照情報のコピーには Set ステートメントを使います。しかしながら VBA の配列はオブジェクトではないため、「Set bbb = aaa」のようには書けず、 参照のコピーにはなりません。
逆に、VB.NET の配列は常に参照型なので、常に参照のコピーとなります。
> 連動して変更されないようにするには、 > bbb = aaa.clone > とする必要があります。
Linq が使えるバージョンであれば、 bbb = aaa.ToArray() とする手もありますよ。Clone だと戻り値が Object 型になりますが、 これなら元の型を維持したデータ型で受け取れます。 (Clone と ToArray の動作は同一では無いですが)
ただ、どちらの方法にしても、「連動して変更されない」ことが保証されるのは コピー元の変数が値型(構造体)の配列だった場合だけです。
もしも値体の配列ではなく、参照型の配列だった場合は、 やはり連動して変更されてしまう可能性があります。
Dim x(0) As System.Net.IPAddress Dim y() As System.Net.IPAddress
x(0) = New System.Net.IPAddress(10)
'配列変数の単純な代入 y = x '下記は True となる(x と y は同じ参照) Console.WriteLine(Object.ReferenceEquals(x, y))
'配列の Clone を代入 y = DirectCast(x.Clone(), System.Net.IPAddress())
'下記は False となる(x と y は別の参照) Console.WriteLine(Object.ReferenceEquals(x, y)) '下記は True となる(各要素の参照がコピーされた) Console.WriteLine(Object.ReferenceEquals(x(0), y(0)))
'Clone だとしても、連動して変更される Console.WriteLine(y(0).Address) '10 x(0).Address = 20 Console.WriteLine(y(0).Address) '20
> aaaがクラスになっており、 > ccc.ddd.eee.fff > のように、長くなってしまう場合に > 見やすくする時に使うためのものなのでしょうか?
そうですね。見やすくするだけでなく、メンバー参照の回数も減るので、 何度も利用する場合は、僅かに高速化にもなるかもしれません。
■No83022 (ぼんかば さん) に返信 > 同じsubの中で参照型を使うメリットは何なのでしょうか?
『参照型を使うメリット』という問いの意図が読めませんでした。
質問内容は「VB.NET における配列のコピー」についてでしたよね。 配列に限らず、コレクションはそもそも参照型として実装されていますので、 複数の値をまとめて取り扱うのであれば、そもそも参照型を使う以外の選択肢がありません。
配列の代わりに IntPtr や C# の unsafe コードを使うということはできますが、 本質的には変わらないでしょう。それに、比較対象とした VBA であっても、 内部的には SafeArray API 経由での読み書きになっているわけで。
それとも、「Dim x() As 値型」と「Dim y() As 参照型」のどちらを使うべきか、という話でしょうか? あるいは、配列が良いか List(Of )等が良いか、という話でしょうか?
> 値型と参照型の違いは > functionやsubで > byvalやbyrefといった使われ方がするので理解しています。
値型と参照型、値渡しと参照渡しはそれぞれ別物ですが、 考え方は似たような物ですし、そこは理解頂いているものとして:
ざっくり言えば、 VBA の場合:IsObject が True を返す変数なら参照型 VB.NET の場合:Class または Interface な変数なら参照型 と言えそうです。まぁ、細かく分類していくともう少し複雑ですが、 配列を別の配列に代入する行為は、VBA であれ VB.NET であれ、基本的に shallow copy です。
しかし、VB.NET の配列は参照型なのに、VBA の方はそうではないため、この点が差異として現れます。
VB.NET の配列は、実体としては System.Array クラスのインスタンスなので、 Single 型が値型であったとしても「Single の配列」は参照型です。 そして VB.NET での配列の代入操作では、「配列そのもの」の参照がコピーされることになります。
一方 VBA の場合、配列そのものは参照型ではないので(実際には SAFEARRAY のポインタですが)、 Set bbb = aaa のような操作はできず、Let 相当の操作となります。そして Let 操作であるが故、 配列そのもののコピーではなく、別の配列が確保され、そこに各要素がコピーされる仕様です。
配列の各要素が参照型で無いプリミティブ型だった場合は、データ内容が複写されますが、 配列の各要素が参照型であった場合は、その参照情報が複製されることになります。
一応、VBA であっても、一つの配列インタンスが複数の変数から同時に 参照される場面はありますが、その場合、一方の変数で配列を操作している 最中に、他方の変数から同一の配列を操作(書込みやリサイズ)すると、 実行時エラー 10 で弾かれる仕様になっています。VBA では、配列そのもの参照を 複製する動作は一般的では無さそうです。VB.NET とは違って。
> このように、異なるsubで二つの使い方があるのは分かるのですが
オブジェクトの「参照」をコピーするだけならば、比較的高速に行えますが、 データも含めた詳細な複製(deep copy)が必要となると、データ量が多くなるにつれ ボトルネックとなってしまうでしょう。
VBA において、ユーザー定義型を ByVal で渡せないのも、 VBA において、配列を ByVal で渡せないのも、 各要素の内容を逐次複製することがボトルネックになるためだと思います。 VBA の LSet ステートメントが使えるような、Bittable な型であれば、 データ量が多くても高速にコピー可能かもしれませんが、それは例外的なものとして。
――VBA 側の事情はさておき、配列やクラスはデータ構造が巨大になりえることから、 VB.NET では、「参照」のコピーがデフォルトの動作になっているのだと思います。 たとえば deep copy が必要なら、MemberwiseClone 等のために ICloneable を実装するということで。
|