のにっき

【C#】stringが参照型だって知ってた?

最近、stringが参照型だということを知って
そう言われるとそうじゃないとおかしいよなー!という納得と
参照型だったらおかしくね!?と思う事があったのでまとめます

参照型と値型の違い

araramistudio.jimdo.com
リンク丸投げですみません!
上記リンクはメモリが図示されていて分かりやすかったのでご参考にしてください
簡単に説明すると
値型は、変数の中に値が格納されていて、
参照型は、変数の中に値が格納されているアドレスが格納されています。
実際にソースの動きで見てみると分かりやすいかも

        private void SetValueTest()
        {
            int testInt = 0;                                    // 値型変数代表
            StringBuilder testStrb = new StringBuilder("000");  // 参照型変数代表
            AddValues(testInt, testStrb);
        }
        private void AddValues(int testInt2, StringBuilder testStrb2)
        {
            testInt2+= 222;
            testStrb2.Append("222");
        }

例として、値型・参照型それぞれの変数を定義。
外部関数の引数として呼び出して関数内で値を変更。
結果、2つの変数にどのような変化が起きるか検証してみます
以下が結果になります

testInt = 0
testStrb ="000222"

違いが分かるでしょうか?
値型の「testInt」は外部関数で変更があっても変わらず0のまま。
参照型の「testStrb」は外部の変更に影響されて"000222"になってます。
つまり、
値型は引数として外部関数に渡される際に新しい変数が作成される。
そのため、外部関数で変更があっても影響しない。
参照型は引数としてアドレスが外部関数に渡される
※同じアドレスが格納された別の変数「testStrb2」が作成されるという事です
外部関数の変更は「testStrb2」に格納されたアドレスの中身に対する変更になる。
つまり、「testStrb」に格納されたアドレスの中身に対する変更でもあるのです。
そのため、外部関数で変更があると、元の変数も影響を受けるという事です。
これが、値型と参照型の違いとなっています。

値型と参照型の分類

ufcpp.net
上記サイトの「C# の型の分類」という図がとても分かりやすいです。
この分類を見ると、
文字列型(string型)は参照型になってる!!
なんてこった!/(^o^)\
今までなんとなくで使ってたstringですが、
経験上stringは値型だと考えて組み込みを行ってました!
これには本当に驚いたとともに、様々な疑問が浮かんできました・・・
なんとなく解決しましたのでそれを以下にまとめます

参照型のstringの挙動はどうなってるの?

参照型・値型の違いを説明する時に用いたコードを
string型の変数で実行してみましょう

        private void SetValueTest()
        {
            string testStr = "000";  // 文字列型変数
            AddValues(testStr);
        }
        private void AddValues(string testStr )
        {
            testStr += "222";
        }

関数の構成は全く同じです。
以下が結果になります

testStr ="000"

外部関数の変更の影響を受けてません・・・
挙動としては値型の挙動になってます。
そうなんです!普段stringを使用していると分かると思うのですが
stringの挙動は値型と同じ挙動をしているのです。不思議です・・・
stringが参照型なのに挙動が値型の理由を以下で掘り下げていきます。

string型にはコンストラクタが存在していた!

docs.microsoft.com
上記サイトに、string型のコンストラクタに関する説明が書かれています。
中でも、以下の記述に注目です!
String(Char[])
 String クラスの新しいインスタンスを、
 指定した文字配列で示された Unicode 文字に初期化します。

簡単にソースで説明すると、

string testStr = "000"; 
string testStr2 = testStr ; 

こうやって記述することは

string testStr = "000"; 
string testStr2 = new string (testStr); 

という事と同義になる。という事です。
普通の参照型変数は変数をコピーするとアドレスをコピーするのですが。
string型変数をコピーすると
別アドレスにコピー元のアドレスの中身をコピーして、
コピー後の変数はコピー後のアドレスになっている
という事です。
参照型なのに無理やり値型の挙動を再現している感じですね。。。
つまり、
string型はコンストラクタによって、
変数コピーの際は値型の挙動をとる!
という事です。
これが参照型のstringが値型のような挙動をとる理由ですね。

なぜstring型は参照型なのに値型みたいに扱おうとしているの?

※ここから先は事実じゃないです。ただの暇つぶしの考察です
まず大前提として、string型の中身はchar型の配列です。
つまり、int型等の値型と違って中身のサイズが不定になってしまいます。
これが、string型が参照型にならざるを得ない理由だと思います。
しかし、参照型は気軽に扱う標準搭載の型としては使い勝手が悪すぎる!
そして、string型はクラス型のように複数の不定の型の集合体ではなく、
char型の配列と、決まった型となっています。
なので、コントラクタを使って値型の挙動を再現できるのだと思います。

以上が参照型云々の話でした。
長々と書いてしまいましたが
string型のコントラクタを発見する場でには
もっと長々とした道中があったのでご勘弁ください。。。