佐々木屋

技術的なことから趣味まで色々書きます

string型は参照型

string型はプリミティブ型に分類されますが、参照型です。よく値型と勘違いされますが誰がなんと言おうと参照型です。
でも、何も考えず以下のようなコードを書くとどうでしょう。結果としては値型のような挙動になります。

string str1 = "Hoge";
string str2 = str1;
str2 = "Piyo";
Console.WriteLine(str1);
Console.WriteLine(str2);
Dim str1 As String = "Hoge"
Dim str2 As String = str1
str2 = "Piyo"
Console.WriteLine(str1)
Console.WriteLine(str2)

参照型ならstr2はnewしていないので、変更を加えると同じ参照先を保持しているstr1にも影響が出て、どちらも「Piyo」になる・・・はずですが、結果は以下になります。

Hoge
Piyo



string型はよく使う型の一つですが、例の通り値型のような動きをするので勘違いしやすいです。
例えば、Hogeをfor文などで何度も+=(&=)演算子で追加するようなプログラムを考えます。以下のようなイメージを持っていませんか。
f:id:sasaki816:20181229175113j:plain


しかしこれは間違いです。
実際は以下のようなイメージです。
f:id:sasaki816:20181229175135j:plain


string型は参照型ではありますが、少し特殊でnewをしなくても変更が加わった瞬間に勝手にnewする型なのです。
つまり、変更がかかる度に内部的に別のインスタンスを作成して新しい箱に値を保持してしまいます。よって最初の例の2行目「string str2 = str1;」という書き方をしても、str2とstr1は値型と同じような「全く別物」として扱われます。


ここで問題になるのが、長文を+=(&=)演算子で追加するような場合です。
これは非常にコストがかかりますので、本来はSringBuilderを利用しなければなりませんが、果たしてどれくらい違うのでしょうか。

速度を測る方法もありますが、今回はメモリ使用量を見てみましょう。以下のテストコードを自分で実行してみて下さい。

string str = System.String.Concat(Enumerable.Repeat("hogehogehogehogehoge", 1000).ToArray());
Console.WriteLine(Environment.WorkingSet);

//string型
string str1 = string.Empty;
for (int i = 1; i <= 1000; i++) {
    str1 += "hogehogehogehogehoge";
}
Console.WriteLine(Environment.WorkingSet);

//StringBuilder
System.Text.StringBuilder str2 = new System.Text.StringBuilder();
for (int i = 1; i <= 1000; i++) {
    str2.Append("hogehogehogehogehoge");
}
Console.WriteLine(Environment.WorkingSet);
Dim str As String = System.String.Concat(Enumerable.Repeat("hogehogehogehogehoge", 1000).ToArray())
Console.WriteLine(Environment.WorkingSet)

'String型
Dim str1 As String = String.Empty
For i As Integer = 1 To 1000
    str1 &= "hogehogehogehogehoge"
Next
Console.WriteLine(Environment.WorkingSet)

'StringBuilder
Dim str2 As New System.Text.StringBuilder
For i As Integer = 1 To 1000
    str2.Append("hogehogehogehogehoge")
Next
Console.WriteLine(Environment.WorkingSet)


結果はどうなりましたか?