C#の文字列の扱いに関するよくある小ネタ。
C#の文字列はクラスですが普通のクラスと振舞いが異なります。
不変型はまだいいですけれど「""って書く度にインスタンスが作成されてしまう」と言う人がたまにいるので、確認も兼ねてパターンを分けてどのような参照になっているか見てみました。
コード
using System;
public class Program
{
public class HogeClass {}
public static void Main()
{
// (1)
var hoge1 = new HogeClass();
var hoge2 = new HogeClass();
Console.WriteLine(Object.ReferenceEquals(hoge1,hoge2)); // (1) False
// (2)
hoge2 = hoge1;
Console.WriteLine(Object.ReferenceEquals(hoge1,hoge2)); // (2) True
// (3)
var str1 = "hoge";
var str2 = "fuga";
Console.WriteLine(Object.ReferenceEquals(str1,str2)); // (3) False
// (4)
var str3 = "hoge";
Console.WriteLine(Object.ReferenceEquals(str1,str3)); // (4-1) True
Console.WriteLine(Object.ReferenceEquals(str2,str3)); // (4-2) False
// (5)
str3 += "fuga";
Console.WriteLine(Object.ReferenceEquals(str1,str3)); // (5-1) False
Console.WriteLine("str1=" + str1 + ", str3=" + str3); // (5-2) str1=hoge, str3=hogefuga
// (6)
Console.WriteLine(Object.ReferenceEquals("",string.Empty)); // (6) True
}
}
結果
(1)においてhoge1とhoge2は同じHogeClassですが違うインスタンスとして作成したので、異なる参照を持ちます。
(2)においてhoge1とhoge2が同じインスタンスを指すようになったので、同じ参照を持ちます。
(3)においてstr1とstr2はそれぞれインスタンスを作成したので、違う参照を持ちます。
(4)においてstr3は別にインスタンスを作成しましたが、文字列の内容が同じstr1と同じ参照を持ちます(4-1)。文字列の内容が違うstr2とは違う参照を持ちます(4-2)。
(5)においてstr3に文字列を追加したところ、同じ参照をだったはずのstr1と違う参照になりました。str1とstr3は違う文字列であり、違うインスタンスです。
(6)において""はstring.Emptyは同じ参照を持ちます。
解説
(1)、(2)については基本的なクラスの振舞いです。その延長で考えると(3)も自然な動きでしょう。
(4)において(1)と同じように別個にインスタンスを作成したはずです。しかし文字列の内容が同じ場合は実は同じ場所を参照しています。つまり、同じ文字列の場合はインスタンスは逐一作成されずに使い回されるという仕組みになっています。
(5)においてstr3に文字列を追加したところstr3は別のインスタンスになってしまいました。普通同じ参照を指しているならstr3を変更した場合str1も同じ値になるはずですが、そうはなりませんでした。string型は不変(イミュータブル)なので後から追加した処理に見えても実際は別のインスタンスが作成されます。この辺りは(4)より知っている人が多いことでしょう。
(6)においてstring.Emptyとは空文字列であり""のことです。同じ文字列なのでこれも同じ参照になります。……実は実際に動かしてみるまで他の記事を読んで""とstring.Emptyは違う参照を持つと思っていたのですが同じになるんですね。
結論
- string型は不変型なので文字列を追加した場合は違うインスタンスが作成される
- 同じ文字列として宣言した場合は同じインスタンスが参照される
- ""とstring.Emptyも同じインスタンスが参照される
おまけ
// (A)
str1 += "fuga";
Console.WriteLine("str1=" + str1 + ", str3=" + str3); // (A-1) str1=hoge, str3=hogefuga
Console.WriteLine(Object.ReferenceEquals(str1,str3)); // (A-2) False
Console.WriteLine(Object.ReferenceEquals(str1,"hogefuga")); // (A-3) False
Console.WriteLine(Object.ReferenceEquals(str3,"hogefuga")); // (A-4) False
Console.WriteLine(Object.ReferenceEquals("hogefuga","hogefuga")); // (A-5) True
後から同じ文字列にしても違うインスタンスですね。同じインスタンスにしてくれるのは宣言時だけでした。