ByRefかByValか への返答

投稿で使用できる特殊コードの説明。(別タブで開きます。)
本名は入力しないようにしましょう。
投稿した後で削除するときに使うパスワードです。返答があった後は削除できません。
返答する人が目安にします。相手が小学生か社会人かで返答の仕方も変わります。
最初の投稿が質問の場合、質問者が解決時にチェックしてください。(以降も追加書き込み・返信は可能です。)
※「過去ログ」について書くときはその過去ログのURLも書いてください。

以下の返答は逆順(新しい順)に並んでいます。

投稿者 cupid  (社会人) 投稿日時 2010/7/5 23:11:21
ああ、これは、ご丁寧に、どうも。 当方はVB4からで、PCでのソフト作りもそこからです。
VB4が出て、PCでのソフト作りが身近になり、救世主の様に思ったものでした。
Windows3.1当時、ソフト開発はC関係でしか行えないと思ってた為、連中に任せてました。
VB2については知りませんでした。─ それにしても色々お詳しいですね。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2010/7/5 11:22:38
雑談ついでに、懐古趣味で VB2 当時の事についても書いておきます。

# VB6 ならいざ知らず、さらに前の VB の話題は、インターネット上には殆ど無いんですよね…。
## 当時はインターネットが普及しておらず、パソコン通信やニュースグループが主体でしたし。


>> VB6のときは、ByRefがディフォルトだったんですね。
> VB6 というか、VB1/VB2 の頃の名残です。
ちなみに VB2 当時は、そもそも ByRef というキーワードすらありませんでした。すなわち、
「値を変更されたくない場合のみ ByVal を付ける」という言語仕様だったのです。
Sub Test1 (ByVal s As String)
    s = "New Text1"
End Sub

Sub Test2 (s As String)
    '省略すると、今でいうところの ByRef 相当の動作となります。 
    'VB2 では、ByRef s As String と記述すると構文エラーとなります。 
    s = "New Text2"
End Sub

Sub Test3 (s As TextBox)
    '下記は OK です。 
    s.Text = "New Text"

    '下記はコンパイルエラーとして処理されます。 
    '「仮引数として指定された変数には Set ステートメントは使えません」 
    'Set s = Me.Text1 

    'なお、仮引数を「ByVal s As TextBox」で宣言するのもコンパイルエラーです。 
    '当時はユーザー定義型だけでなく、オブジェクト変数も値渡しできなかったのです。 
End Sub


そのため、キーワードとしての ByRef が登場した後も、デフォルトが参照渡しであることは
変更できなかったのです。歴史的な事情から、ByVal をつけるのは開発者の意図である事が明らかですが、
ByRef をつけないことは、開発者が参照渡しにすることを想定していたかどうかが分からないからです。


> VB4以降とそれより前とでは型システムが微妙に異なるのですが、
この型システムについてですが、VB2 当時は利用可能なデータ型が非常に限定されていました。

というのも、クラスモジュールの登場は VB4 からであり、当時は自作可能な型といえば、
Form1 や Form2 などのユーザー定義のフォーム型、それにユーザー定義型だけだったのです。

当時のプリミティブ型は、Integer、Long、Single、Double、Currency、String、Variant の
7 種であり、これに固定長文字列型である String * n を加えた物だけが、利用可能な
プリミティブ型宣言のすべてでした。

 ・なお、これら 7 種類(固定長文字列型も含めれば 8 種類)のデータ型の中から、
  万能型である Variant を除いた 6 種類には、それぞれ型宣言文字 %,&,!,#,@,$ が
  割り当てられています。この仕様は VB6 まで受け継がれる事になり、VB6 の時代においても、
  Byte、Boolean、Date、Object、Variant に対しては型宣言文字が用意されることはありませんでした。
 ・ちなみに、型宣言文字という概念は Visual Basic の時代に用意された物では無く、その前身たる
  BASIC 言語の時代から存在していた物です(ただし、処理系によっては使用できない型文字もありました)。
 ・当時 `配列` とは、型ではなく変数の一形態でした。ゆえに「As Integer()」のような宣言は
  できませんし、Variant 型に配列を格納する事も出来ませんでした(Variant 配列は作れましたが)。
 ・定数(vbInformation など)はありましたが、列挙型(VbMsgBoxStyle など)はありません。
  列挙型が登場したのは、VB4 の時代になってからです。
 ・なお、vbModal や vbModeless といった定義が登場したのも VB4 からです。VB6 のヘルプにおいて、
  Form の Show メソッドや ZOrder メソッドの引数説明に、定数(列挙型の値)が利用されておらず、
  単に 0 や 1 といったマジックナンバーが使われているのも、VB2 当時のヘルプの名残です。
 ・False や True というキーワードはありましたが、Boolean という型はありませんでした。
  Variant 変数に代入した場合、False は Integer 型の 0、True は -1 で管理されました。
  このため、以降のバージョンで登場した Boolean 型は、1 bit 型や 1 バイトの型ではなく、
  16bit(2 バイト)の型として用意される事になりました。
 ・日付型を内部形式として持つバリアント型(VarType = 7)はあったものの、
  プリミティブなデータ型としての Date 型はまだ存在していませんでした。
  DateSerial や DateAdd の戻り値が Date 型では無いのも、当時の流れを組んでいるからです。
 ・日付型が存在しないため、当時は CDate 関数もありません。その代わりに、VarType 7 (日付)な
  バリアント型に変換するための CVDate 関数がありました(VB6 でも下位互換性の為に残っています)。
 ・ちなみに、Byte 型もありませんでした。Object/Date/Boolean は VB4 で登場した型です。


> 今でいうところのVariant型や総称Object型を扱う分には、
上記にも書きましたが、VB2当時はまだ As Object という宣言は許されていませんでした。
利用可能なオブジェクト型とは、以下のものだけです。

 ※スペシフィックな型※(固有な型とか具体的な型といった意味)
  MDIForm
  ユーザー定義のフォーム型(Form1、Form2 等)
  標準コントロール型(TextBox、Label、PictureBox 等)
  カスタムコントロール型(Grid、OLEClient 等)

 ※ジェネリックな型※(総称的な型とか汎用的な型といった意味)
  Form (MDIForm +ユーザー定義のフォーム型)
  Control (標準コントロール型+カスタムコントロール型)


なお VB6 以降では、それぞれ「固有オブジェクト型」「総称オブジェクト型」という表記法に
変わっており、スペシフィックやジェネリックといった用語は使用されなくなっています。
(ジェネリック型については、VB2005 から別の意味合いで使われるようになりましたけれどね)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2010/7/4 12:17:22
ByRef にしてしまうと、たとえば As Long な引数に As Integer な変数を渡せなくなるなど、
呼び出し上の不便さも発生しますね。

> VB6のときは、ByRefがディフォルトだったんですね。
VB6 というか、VB1/VB2 の頃の名残です。

VB4以降とそれより前とでは型システムが微妙に異なるのですが、今でいうところのVariant型や
総称Object型を扱う分には、16bit当時は参照渡しの方がパフォーマンスが良かったのです。

しかし、メソッドの呼び出しが単一アプリ内で完結していたころは良かったのですが、機能拡張により
プロセス間通信が行えるようになった後継バージョンにおいては、マーシャリングの手間が
馬鹿にならないケースが増え、特にVB5以降では ByValの方が好ましいとされるようになりました。
(また、ByVal/ByRefの違いというのは、呼び出し側のコードを見ただけでは違いが分からないため、
 カプセル化に際し、コードが分かりにくくなってしまうために非推奨にされたという経緯もあります)

しかし、ByVal/ByRefの省略時の動作を変更してしまうと、互換性上の多大な問題を残すため、
省略時の動作の切り替えは、VB7すなわち.NET 版の登場まで見送られていたというわけです。
# それとて、切り替えには賛否両論あったわけですが。


> 不用意に引数に入れた変数が、いつの間にか値が変っていた、なんて事故を避ける為にか?
もうひとつ、下記のような分かりにくい問題を避ける意図もあります。
http://support.microsoft.com/kb/216481/ja


> 整数配列を引数に入れて、sub内で内容変更させると、
> ByValであっても、変更内容がCall元に戻るのですね。
これは、配列が「参照型」であるためです。
もしも配列の中身を操作するのではなく、subOutArg メソッド内で「aTb = 新しい配列」といった
配列そのものの差し替えを行う場合においては、ByVal と ByRef の差が明確となります。


> VB6では、ByValで配列全体を引数に設定できませんでした。筋が通っていますね。
この違いは、VB6 でも同様です。
http://support.microsoft.com/kb/161308/ja

VB6 以下では配列をByValで渡せませんが、オブジェクト(これも参照型です)を渡した場合には、
今回の件と同様の現象が発生します。たとえば、TextBox をByVal で渡そうと ByRef で渡そうとも、
メソッド内で TextBox の Text プロパティを編集することができます。ただし引数が指し示す実体を
別の TextBox への参照に差し換えようとする場合においては、ByRef でなければなりませんが。


> VBには言語仕様的に変な所もある、という事かな。
VB以外の言語でも同様の動作になりえますよ。

なお、ByRef については、VB6 から変更された点がもう一つあります。
たとえば、Sub Test(ByRef s As String) なメソッドがあり、ここに TextBox1.Text のような
プロパティを渡すとします。この場合、VB6 までは「式」を渡されたものとして扱われ、
Text プロパティの内容までは書き変わらなかったのですが、VB.NET では変更されます。

   Call Test(TextBox1.Text)

Sub Test(ByRef s As String)
    s = "New Text"
End Sub
投稿者 cupid  (社会人) 投稿日時 2010/7/3 14:56:56
表記について、貴サイトのBBSや、同過去ログなど見ますと、かなり話題にされたようですね。
VB.Netでは、引数にByRefを使うのは出来るだけ止めましょう、という考えはある程度分かります。
が、確か、VB6のときは、ByRefがディフォルトだったんですね。
MSも、ByRefは推奨せず(と書いた所を見たこと無いですが)、と言うと、急に自己批判された?

引数に値を出力したい場合、ByRefにするわけですが、自分の趣味としては、気にしません。
確かに、組織でソフト開発する場合、そういう規約をもうけるのは、理解できなくもありません。
不用意に引数に入れた変数が、いつの間にか値が変っていた、なんて事故を避ける為にか?

かなり昔、Fortranですが、引数に定数を入れたとき、その定数の内容までも変えられた、
そういう言語もありました。しかし今は、VB6でも、そこまでの事は無いようです。
であれば、ByRefが拙いと、大声で言う程の事でも無いように思えます。

ついでに試してみたら、VB2005Expressですが、整数配列を引数に入れて、sub内で内容変更
させると、ByValであっても、変更内容がCall元に戻るのですね。 そもそも、
引数に配列を入れるなんて、ソフトデザインが変と言われるかもしれませんが、可能は可能です。
VB2005Helpには、事情が解説されてますが、VBには言語仕様的に変な所もある、という事かな。
なお、VB6では、ByValで配列全体を引数に設定できませんでした。筋が通っていますね。
(参考)
Private Sub subOutArg(ByVal aTb() As Integer)
  Dim i As Integer
  For i = 0 To UBound(aTb)
     aTb(i) = ...(略)...
  Next i
End Sub