どう使い分ける?値渡しと参照渡し

2009年11月12日公開

現在世の中で使われている多くのプログラミング言語において、関数は「引数」として情報を受け取る機能を有しています。この引数として渡される変数の「引き渡され方」は、大きく分けて変数そのものを渡す「値渡し」と変数に対するアドレス情報を渡す「参照渡し」が存在し、それらの振る舞いはプログラミング言語の種類により異なります。

そこで、このページでは値渡しと参照渡しについて、両者の処理方法の違いをあらためて掘り下げて考えてみたいと思います。

グレープシティ テクニカルエバンジェリスト
八巻 雄哉

2003年1月入社。PowerToolsシリーズのテクニカルサポートを担当するかたわら、製品開発やマーケティングにも従事。2006年から、.NETテクノロジーとPowerToolsシリーズ普及のためエバンジェリストとして活動中。
Microsoft MVP for Development Platforms -Client App Dev Jan 2009 -Dec 2012

デフォルトは「値渡し」

Visual Basicでは、値渡しはByVal、参照渡しはByRefというキーワードを引数に付加します。このキーワードは省略可能で、省略したときの振る舞いはVB6以前では参照渡し、.NET以降のVisual Basicでは値渡しと異なっています。

省略して記述されているソースコードをVisual Basicアップグレードウィザードを使って.NETのVisual Basicコードへ変換した場合には、自動的にByRefキーワードが付加されるため大きな問題にはなりません。しかしながら、実際に省略して記述していたという人は .NETで振る舞いが変わることを理解しておく必要があるでしょう。

なお、.NETではVisual StudioでByVal/ByRefを省略して記述しようとするとIntelliSenceによって自動的にByValが補完されます。

単純に値渡しと参照渡し、つまり「ByValキーワードとByRefキーワードのどちらを使用するべきか?」という問いであれば、その答えは簡単です。

可能な限りByValキーワードを使用する。

このことは、先の省略した場合にIntelliSenceによって自動的にByValが補完されるという動作に加え、Visual Studio Team Systemのコード分析に下記のような規則が存在していることからも明らかです。

型を参照渡ししないでください

可能な限りByValキーワードを使用すべき理由は、呼び出し元のコードの変数がプロシージャによって変更されるのを防ぐことができるという点です。

これは「AさんからBさんへのビデオテープ(オリジナルムービー)の貸し出し」を例に考えてみると分かりやすいでしょう。
値渡しは撮影したマスタテープをビデオテープにダビングしてBさんへ貸し出す方法です。貸し出しているのは複製物であるため、誤ってBさんがそのビデオテープの内容を上書きしてしまった場合でもAさんのオリジナルムービーは無事です。一方、参照渡しはマスタテープの保管場所をBさんに教え、そのビデオテープをAさんとBさんで共有して視聴する方法になります。そのため、誤ってBさんがビデオテープを上書きしてしまった場合にはAさんは怒り心頭に発してしまうというわけです。

では、実際のコード例を見てみましょう。以下のコード例では、呼び出し元のプロシージャにおいて値渡しされた変数aの値はTestメソッドの実行後でも値が1のままとなっているのに対して、参照渡しされた変数bの値はTestメソッドの実行後に2に変更されています。

Sub Main()
    Dim a As Integer, b As Integer
    a = 1
    b = 1
    Test(a, b)
    Console.WriteLine("a={0}", a)
    Console.WriteLine("b={0}", b)
    Console.ReadKey()
End Sub
 
Private Sub Test(ByVal a As Integer, ByRef b As Integer)
    a += 1
    b += 1
End Sub

実行結果 :

a=1
b=2

呼び出し元のコードの変数を変更できる参照渡しの場合、呼び出し元からは引数として渡した変数が変更されるのかどうかですら判断がつきません。このことはコードの可読性を落とすだけでなく、バグを作りこむ原因にもなります。

「参照渡し」のメリットは?

では、参照渡しが必要となるのはどのような場合でしょうか?参照渡しのメリットとして、一般的にはパフォーマンスが挙げられます。これは、変数の「参照」をコピーする参照渡しに比べて変数の「値」をコピーする値渡しのほうがオーバーヘッドは大きくなるという考えによるものです。

しかし .NETではパフォーマンスという観点で参照渡しを使用したほうが良いケースというのは稀です。.NETの変数のデータ型は値型と参照型の2種類に大別されます。MSDNライブラリの「値型と参照型」の記載によると、下記のように分けられています。

値型

  • ・すべての数値データ型
  • ・Boolean、Char、および Date
  • ・すべての構造体(メンバが参照型の場合でも)
  • ・列挙型(基になる型が常に SByte、Short、Integer、Long、Byte、UShort、UInteger、または ULong であるため)

参照型

  • ・String
  • ・すべての配列(要素が値型の場合でも)
  • ・クラス型(Form など)
  • ・デリゲート

実は、値渡しをする場合であっても変数が値型か参照型かによって、その振る舞いは大きく異なります。たとえば以下のコード例の場合、値型である構造体SPersonのNameフィールドは”Yamaki”のままとなっているのに対して、参照型であるクラスCPersonのNameフィールドは”YamakiYuya”と変更されてしまいます。

Sub Main()
    Dim s As New SPerson
    Dim c As New CPerson
    s.Name = "Yamaki"
    c.Name = "Yamaki"
    Test(s, c)
    Console.WriteLine("SPerson.Name={0}", s.Name)
    Console.WriteLine("CPerson.Name={0}", c.Name)
    Console.ReadKey()
End Sub

Private Structure SPerson
    Public Name As String
End Structure

Private Class CPerson
    Public Name As String
End Class

Private Sub Test(ByVal s As SPerson, ByVal c As CPerson)
    s.Name += "Yuya"
    c.Name += "Yuya"
End Sub

実行結果 :

SPerson.Name=Yamaki
CPerson.Name=YamakiYuya

これは、同じ値渡しであっても値型はメモリ内(スタック)に値が格納されるのに対して、参照型は値を保持する別のメモリ(ヒープ)の場所を示すポインタが格納されるためです。つまり、参照型の変数を値渡しした場合、変数そのものを変更することはできなくとも変数が参照するインスタンスのメンバは変更することができるのです。

このことは、MSDNライブラリの「引数の値渡しと参照渡しの違い」に記載されており、下記の表のようにその振る舞いを分類することができます。

要素の型 ByVal で渡す場合 ByRef で渡す場合
値型(格納されるのは値のみ) プロシージャは、変数およびそのメンバを一切変更できません。 プロシージャは、変数およびそのメンバを変更できます。
参照型(クラスまたは構造体のインスタンスへのポインタを格納) プロシージャは、変数を変更することはできませんが、変数が指すインスタンスのメンバを変更できます。 プロシージャは、変数および変数が指すインスタンスのメンバを変更できます。

つまり、参照型を値渡しした場合には変数の値ではなくデータを保持する別のメモリのポインタが値としてコピーされていることになるため、パフォーマンスへの影響はありません。
値型を値渡しした場合にはコピーにかかるオーバーヘッドを無視できない場合があります。おもに構造体などデータサイズの大きい値型の変数を扱う場合には参照渡しの使用を検討すべきです。

参照渡しが必要となるもう1つのケースは、異なる性質の結果を複数返す関数を実装したい場合です。このような実装は、.NET FrameworkクラスライブラリのTryParseメソッドなどで確認することができます。

TryParseメソッドは、変換の成功の可否をBoolean型の戻り値として返すほかに、第2引数が参照渡しとして実装されていることで変換結果も同時に受け取れるようになっています。このような実装は例外的と言えますが、このことからも「参照渡しを使ってはいけない」のではなく、「可能な限り値渡しを使用する」ということが見て取れます。

開発者はどう使い分けている?

関数を作る際の記述方法に関するアンケートを2009年10月22日に配信したPowerNews 290号で行いました。回答結果と皆さまからいただいたコメントを紹介します。

質問内容

実務で関数(メソッド)を作る際、どちらを多用しますか?

その1

---------------------------------------------
Sub Hoge(ByRef aStr As String)
   <処理内容>
End Sub
---------------------------------------------

その2

---------------------------------------------
Function Hoge(ByVal aStr As String) As String
   <処理内容>
End Function
---------------------------------------------

回答結果

有効回答数:66
アンケート実施日:2009/10/22
グラフ「PN290アンケート結果」

皆さんからいただいたコメント

その1(Sub + ByRef)を選んだ方のコメント
両方使うが、構造体(CLASS)などをそのまま渡して戻したい時が頻度が高いため。
一応Subですけど、どちらを多用というか、「値を返す・返さない」で使い分けなので。ちょっと質問とずれるかも。
多用と言うか、作成しなければいけない関数の形に合わせて選びます。
リターン値が不要ならSubで、リターン値が必要ならFunctionで。

その2(Function + ByVal)を選んだ方のコメント
ref引数は嫌いです!
.NETのByRefはプロパティに値が戻せるので使いやすくなりましたが、出力値が1個ならやはりByValにして戻り値に返す方が使いやすいでしょう。
(その2は)パラメータの内容が保持される為、汎用性がある。
戻り値がひとつの場合は断然Function。呼び出し側の記述がスマートになるので。
Functionだと、他のライブラリ呼び出しと呼び出し形式を統一できるし、実際には戻り値が無い関数(メソッド)はあまり無いから。
処理内容で使い分けていますね。
処理結果が正常か異常かを返してほしい場合が多いので,以下のパターンもよく使います。

Function Hoge(ByRef aStr As String) As Boolean
Try
Hoge = true
Catch
Hoge = false
End Try
End Function
Subプロシージャだと、戻り値がないから。
戻り値を使って、色々処理を行うロジックを書くクセがあるので。
返値を持っている方がシンプルに構成できます。
引数がByValだから。
関数として戻り値があることが目視できるのでこちらにしています。
基本的にバッチを組むのが仕事なので
SQL文みたいな処理の流れを掴みたい時に邪魔する長いのを外に逃がすって場合が多い。
参照渡しだと、障害発生で調査する際にデバックなどで追いにくいから使用しないです。あと、戻り値の型が分からないとバグの温床になりそうだから、型指定はします。
戻り値が1つならば、関数のほうが分かりやすい。
出力引数はまず使わない。呼び出し側のコードを見たときに、入力引数と区別できないから。C# ならば out / ref の有無で区別できるが、いずれにせよ、戻り値の方が扱いやすい。
今どき「1」を選択する人はいないでしょう。
シンプルにコードが書けるし、分かりやすい。
その関数が正常に働いたかを、戻り値で返すため
( C言語はこの方式)。
functionも使ってあげたい。
ケースバイケースですが…。
私はたとえ返り値が必要ない関数であっても
処理が成功したかどうかくらいは返すべきだと思います。
殆どの場合、関数の戻り値で処理します。ByRefはなるべく使いたくないです。
理由は二つあります。
(1)引数の渡し方
 ほとんどが値渡しを使用する場面が多いですし、
 .NETになってから、規定値がByValになりましたから、
 そのまま使っても問題ないと思います。
 ただ、ByRefを使うこともたまにはあります。
(2)戻り値について
 Funcitonキーワードで作成しておけば、戻り値が必要で
 無くなっても使わなければいいので、両方の状態が
 可能な方が修正が楽です。
戻り値が欲しい場合は、Functionを使います。僕の場合は、処理結果値が欲しい場合が多いです。(C#ですけど)
やっぱり[ByVal]でしょう。
IN(引数)とOUT(戻り値)は明確に分けるべき。
VBでは今までなるべく参照渡しを使わないようにしてきましたが、本当にこれで良かったのか今思うと疑問です。
例え、戻り値の無いメソッドであっても、処理が成功したか失敗したかなどの情報を戻り値として返す事で、エラーをプログラム側で制御できると云った事などもしていますので、Functionを使います。
私の場合、メソッドの約8割以上がFunctionだと思います。
昔からのクセですね。
処理戻り値は必ず戻すように心掛けている。
参照渡しによる値の変更は、コードの可読性を落とす一因です。
値を変更する関数なら、その意図を持ってInputとOutputを明確にする方がよいと思います。
この記述のほうがわかりやすく感じる。
IN/OUTは基本的に別にします。
.NET Frameworkのメソッドって、ほとんどこっちですよね?
なのでそれに沿って。
参照渡しだと,引数に入れた変数の内容が改変されるのかどうかが,呼び出し側のソースコードからは判読できないので可能な限り使用しません。

関連記事

  1. » 戻り値の指定、VBとVB.NETでどう違う?
  2. » どう使い分ける?文字列の連結方法