VB のたまご

作成日: 2015/12/03, 更新日: 2015/12/03


値型と参照型は何が違うのか、基礎から学ぼう

突然、値が変わる!?

  •  その昔、DataTable の使い方が分かってきた頃に、不思議な現象に出会いました。
  • 引数で DataTable データを渡しているだけで、戻り値を受け取らないメソッドがあり、そのメソッドを通った後で、 なぜか、渡した DataTable データの値が変わっているのです。 戻り値を受け取っていないのに、DataTable データの値が変わっているのです。気持ち悪!

  •  そもそもメソッドとは、戻り値無しのメソッド=呼び出し元に影響を与えない、戻り値ありのメソッド=呼び出し元に変更後の値を返す。 だろうが。という認識です。

  •  私の Visual Studio バグった?と思わずにはいられませんでした。
  • あ、さっき Windows Update したからか?再現調コースか?等と、環境の問題に決めつけようとする当時の私。 これで1日くらい、ハマりました。

  •  そう、値型と参照型を分かっていなかったのです。
  • 先輩に教えていただかなかったら、無限ループするところでした。

スポンサーリンク


宣言できる型の種類ではない

  •  パッと見、あぁ、Integer 型とか String 型みたいなやつでしょ。分かる分かる。と思ってしまいますが違います。 値型、参照型というのは、宣言できる型の1つではありません。なので、以下のような使い方はできません。
  • Dim a1 As 値型
    Dim a2 As 参照型
    

値型、参照型とは?

  •  値型、参照型で調べると、メモリ上の格納領域が~、という専門的な説明が多くみられます。 なかなか難しい内容です。今でも何となく分かる程度です。さぁ、10年目の今の私よ! 持っている知識をフル活用し、分かりやすい解説を見せつけて、当時の私をギャフンと言わせてやるのだ! 今こそ思い知らせるのだ!

  •  ちょうどいい機会なので、値型と参照型について、復習がてら改めて調べました。 冒頭では、引数の値渡し、参照渡しの事を言っていますが、それを理解するには、まず、値型と参照型を理解しなければいけません。 当時の私、頑張って!

スポンサーリンク


値型について

  •  まずは、動かしてみてどういう動きか知ることが大事です。ここでは、値型代表として構造体型に登場してもらいます。
  • ' 値型
    Dim s1 As New S1 With {.Name = "before", .Age = 20}
    Dim s2 As S1 = s1
    s2.Name = "after"
    s2.Age = 30
    
    Console.WriteLine("Name = {0}, Age = {1}", s1.Name, s1.Age)
    Console.WriteLine("Name = {0}, Age = {1}", s2.Name, s2.Age)
    
    Structure S1
        Public Property Name As String
        Public Property Age As Integer
    End Structure
    
    ' 出力結果
    Name = before, Age = 20
    Name = after, Age = 30
    

  •  値型の場合、変数に、実際の値を入れて管理します。
  • s1 を定義した際、s1 変数に、s1 用の値がセットされます。
  • s2 を定義した際、s2 変数に、s1 用の値がセットされます。

  •  2つの変数に入っている値は、それぞれの変数の中に入っているので(別々の場所で管理しているので)、 後から s2 の各値を変更しても、変わるのは s2 変数に入っている値だけです。そのままですが、これが値型の特徴です。

参照型について

  •  続いて参照型です。ここでは、参照型代表としてクラス型に登場してもらいます。と言っても、中身は全く同じです。
  • ' 参照型
    Dim c1 As New C1 With {.Name = "before", .Age = 20}
    Dim c2 As C1 = c1
    c2.Name = "after"
    c2.Age = 30
    
    Console.WriteLine("Name = {0}, Age = {1}", c1.Name, c1.Age)
    Console.WriteLine("Name = {0}, Age = {1}", c2.Name, c2.Age)
    
    Class C1
        Public Property Name As String
        Public Property Age As Integer
    End Class
    
    ' 出力結果
    Name = after, Age = 30
    Name = after, Age = 30
    

  •  c2 の各値を変更したら、変更していないはずの c1 の各値も変わってしまいました。
  • 参照型の場合、変数には、実際の値ではなく、実際の値がある「住所」データを入れて管理すると言われています。
  • 実際の値を直接手元に置かないで、どこかの場所にあり、その場所で管理していて、変数には目印だけ入れておく、という管理方法をします。

  •  これは、お金を銀行に預けておいて、通帳だけ手元にある、という場面に似ています。
  • お金を全部財布に入れて管理するということは、手元にあるという安心感とは逆に、重くなる、かさばる、という不便さが生まれます。 通帳だけであれば、お金が増えても軽いままで、かさばることもありません。 同じように、分かりやすい値型にも不便さがある、ということです。

  • イメージ
  • 値型

  • イメージ


  •  現実的には、c1, c2 と同じように、同じ口座用の通帳を複数作成することはできませんが、 この例えだと、イメージしやすくなりませんか?
  • c1 通帳が指し示す口座と、c2 通帳が指し示す口座は同じ口座です。
  • c2 通帳を使って ATM でお金を増減したら、c1 通帳を記帳することで同じ増減額になります。
  • つまり、c1 と c2 が同じ目印で、実際の値も同じ場所にあるため、変更していないはずの c1 の各値も変わるという動きになります。


  •  それでは、以下の場合はどうなるでしょうか?
  • Dim c3 As New C1 With {.Name = "before", .Age = 20}
    Dim c4 As New C1 With {.Name = "after", .Age = 30}
    
    Console.WriteLine("Name = {0}, Age = {1}", c3.Name, c3.Age)
    Console.WriteLine("Name = {0}, Age = {1}", c4.Name, c4.Age)
    
    ' 出力結果
    Name = before, Age = 20
    Name = after, Age = 30
    

  •  この場合、変数宣言しているだけで、変数に変数をセットしていないので、2つとも別々の値になります。


  •  それでは、以下の場合はどうなるでしょうか?
  • Dim c5 As New C1 With {.Name = "before", .Age = 20}
    Dim c6 As C1 = c5
    c6 = New C1 With {.Name = "after", .Age = 30}
    
    Console.WriteLine("Name = {0}, Age = {1}", c5.Name, c5.Age)
    Console.WriteLine("Name = {0}, Age = {1}", c6.Name, c6.Age)
    
    ' 出力結果
    Name = before, Age = 20
    Name = after, Age = 30
    

  •  この場合、いったんは、c5 と c6 が同じ目印になるので、実際の値も同じになりますが、 最後に c6 側が、違う値の準備と「違う値への目印」をセットするので、別々の値になります。


  •  それでは、以下の場合はどうなるでしょうか?
  • Dim c7 As New C1 With {.Name = "before", .Age = 20}
    Dim c8 As C1 = c7
    c8.Name = "after"
    c8.Age = 30
    c8 = New C1 With {.Name = "after", .Age = 30}
    
    Console.WriteLine("Name = {0}, Age = {1}", c7.Name, c7.Age)
    Console.WriteLine("Name = {0}, Age = {1}", c8.Name, c8.Age)
    
    ' 出力結果
    Name = after, Age = 30
    Name = after, Age = 30
    

  •  この場合、いったんは、c7 と c8 が同じ目印になり、さらに、c8 経由で実際の値を更新したので、それぞれ更新後の値になります。 最後に c8 側が、違う値の準備と「違う値への目印」をセットするので、別々の値になります。 同じ値ですが、インスタンス生成するということは、新しい場所に準備する、という動きをするため、 仮にこの後で、c8 の各値を変更しても。c7 の各値が変わることはありません。


  •  それでは、以下の場合はどうなるでしょうか?
  • Dim s1 As String = "before"
    Dim s2 As String = s1
    s2 = "after"
    
    Console.WriteLine("s1 = {0}", s1)
    Console.WriteLine("s2 = {0}", s2)
    
    ' 出力結果
    s1 = before
    s2 = after
    

  •  何回やんねん!と思われた方、これで最後なのでもう少しだけお付き合いください。
  • String 型は参照型である、というのはご存知でしょうか?参照型なのであれば、s1 の目印が s2 にセットされたのだから、 出力結果は同じ値になるはずです。しかし結果は違っています。実は、こういうことなんです。
  • s2 = "after"
            ↓
    s2 = New String("after")
    

  •  実際にこのような入力をすると、引数の渡し方が間違っている旨のエラーが出てしまいますが、 文字列をセットする時に、インスタンス生成しています。インスタンス生成しているので、 違う値の準備と「違う値への目印」をセットするので、別々の値になります。

  •  次は、値渡しと参照渡しについてです。

  •  最後までこの記事を読んでいただき、ありがとうございました。

  • スポンサーリンク