SORA.GetOutput()

備忘録と捌け口とシェアと何か。

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怖い!!