C# 参照型の変数は、値渡しと参照渡し(ref)でどのように違いが出るか
値型、参照型、ref、out
TOACHさんの記事で、値型と参照型それぞれの場合について refとoutの使い分けがわかりやすく書かれています。
C++から移ってきたアナタへ。C#のref, outの使い方について。toach.click
記事では、
呼び出し元のオブジェクトを書き換える場合、参照型は修飾子なしでもrefでも同じ動作になる
とあります。「じゃあ呼び出し元のオブジェクトを書き換える場合以外」ってどんなパターンなの?というのを検証してみました。
参照型変数の値渡しと参照渡しで結果が同じなるパターン
渡した先での操作対象が参照の指し示す先の実体の場合、
値渡し・参照渡しで違いはありません。
※コード引用させていただきました。
public static void Main(string[] args) { // For reference type object. var referenceTargets = new List<string> { "a", "b", "c" }; const string separator = ", "; // [修飾子なしバージョン] アウトプットは a, b, c, No ref and out. (書き換わる) AddText(referenceTargets); Console.WriteLine( "referenceTarget : " + string.Join(separator, referenceTargets.Select(target => target.ToString()))); // [refバージョン] アウトプットは a, b, c, No ref and out., ref (書き換わる) AddText(ref referenceTargets); Console.WriteLine( "referenceTarget : " + string.Join(separator, referenceTargets.Select(target => target.ToString()))); } private static void AddText(List<string> targets) { targets.Add("No ref and out."); } private static void AddText(ref List<string> targets) { targets.Add("ref"); }
参照型変数の値渡しと参照渡しで結果が変化するパターン
渡した先での操作対象が参照情報そのものの場合、
値渡し・参照渡し(ref付きの有無)で挙動が変化します。
※コード引用させていただきました。
※一つ目とのコードの違いは、通常バージョンとrefバージョンのaddTextメソッド内に
targets = new List<string> { "e", "f", "g" };
を追加しただけです。
public static void Main(string[] args) { // For reference type object. var referenceTargets = new List<string> { "a", "b", "c" }; const string separator = ", "; // [修飾子なしバージョン] アウトプットは a, b, c (書き換わらない) AddText(referenceTargets); Console.WriteLine( "referenceTarget : " + string.Join(separator, referenceTargets.Select(target => target.ToString()))); // [refバージョン] アウトプットは e, f, g, ref (書き換わる) AddText(ref referenceTargets); Console.WriteLine( "referenceTarget : " + string.Join(separator, referenceTargets.Select(target => target.ToString()))); } private static void AddText(List<string> targets) { targets = new List<string> { "e", "f", "g" }; targets.Add("No ref and out."); } private static void AddText(ref List<string> targets) { targets = new List<string> { "e", "f", "g" }; targets.Add("ref"); }
なにがちがうの?
参照型を値渡しした場合、参照の関係はこのようになっています。
(Mainメソッド内)
referenceTargeds
-> (referenceTargetsの実体)
(Addメソッド内)
targets
-> (referenceTargetsの実体)
★この状態でtargets = new List<string>()
すると、targetsの参照先のみが新しく作ったリストに変更される。
一方、参照型を参照渡しした場合、参照の関係はこうです。
※少しわかりにくいですが、参照型を参照渡しをした場合には、渡し元の参照への参照が渡されます。
(Mainメソッド内)
referenceTargeds
-> (referenceTargetsの実体)
(Addメソッド内)
targets
-> referenceTargets
-> (referenceTargetsの実体)
★この状態でtargets = new List<string>()
すると、targetsの参照先のreferenceTargetsの参照先が新しく作ったリストに変更される。
結論
ref怖い!!