5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

値渡し / 参照渡し と 値型/参照型 をごっちゃにしてしまう君に告ぐ 不変な参照型 は 値型と見分けがつかないぞ

Last updated at Posted at 2025-04-17

値渡しと参照渡しの動作確認を 値型と参照型を例にしつつ 参照戻り値対応している配列で試してみる

C# では 配列のインデクサが参照戻り値対応しているのでそれでいってみます。

値型(かへn)

例えばこういった構造体(値型)があったとするじゃないですか。

record struct V1(string Value);

C# でいう record struct は可変な 構造体レコードです。

次の様な操作をするとどうなるでしょうか?

V1[] array = [new("1"), new("2"), new("3"), new("4"), new("5")];
Console.WriteLine($"1:before: {ToString(array)}");
int i = 0;
{
    // 0: 配列のインデクサは参照戻り値
    array[i++].Value = "🐁";
}
{
    // 代入(値渡し)
    {
        var v = array[i++];
        //1: プロパティを変更
        v.Value = "🐄";
    }
    {
        var v = array[i++];
        //2: インスタンスを更新
        v = v with {
            Value = "🐅",
        };
    }
}
{
    // ref ローカル変数 (参照渡し)
    {
        ref var v = ref array[i++];
        //3: プロパティを変更
        v.Value = "🐈";
    }
    {
        ref var v = ref array[i++];
        //4: インスタンスを更新
        v = v with {
            Value = "🐒",
        };
    }
}
Console.WriteLine($"1:after : {ToString(array)}");
string ToString<T>(T[] array) => string.Join(",", array.Select(v => $"{v}"));
1:before: V1 { Value = 1 },V1 { Value = 2 },V1 { Value = 3 },V1 { Value = 4 },V1 { Value = 5 }
1:after : V1 { Value = 🐁 },V1 { Value = 2 },V1 { Value = 3 },V1 { Value = 🐈 },V1 { Value = 🐒 }

Index でいうところの
0: 反映される(インデクサから直接プロパティ変更)
1: 反映されない(値渡し+プロパティ変更)
2: 反映されない(値渡し+インスタンス変更)
3: 反映される(参照渡し+プロパティ変更)
4: 反映される(参照渡し+インスタンス変更)
となります。

参照型(可変)

例えばこういったクラス(参照型)があったとするじゃないですか。

record class V2(string Value) {
    public string Value {get;set;} = Value;
}

(※record class はデフォルトで get; init; で読み取り専用なのでわざわざ書き換えられる様にしている)

次の様な操作をするとどうなるでしょうか?


V2[] array = [new("1"), new("2"), new("3"), new("4"), new("5")];
Console.WriteLine($"2:before: {ToString(array)}");
int i = 0;
{
    //0: 配列のインデクサは参照戻り値
    array[i++].Value = "🐁";
}
{
    // 代入(値渡し)
    {
        var v = array[i++];
        //1: プロパティを変更
        v.Value = "🐄";
    }
    {
        var v = array[i++];
        //2: インスタンスを更新
        v = v with {
            Value = "🐅",
        };
    }
}
{
    // ref ローカル変数 (参照渡し)
    {
        ref var v = ref array[i++];
        //3: プロパティを変更
        v.Value = "🐈";
    }
    {
        ref var v = ref array[i++];
        //4: インスタンスを更新
        v = v with {
            Value = "🐒",
        };
    }
}
Console.WriteLine($"2:after : {ToString(array)}");
string ToString<T>(T[] array) => string.Join(",", array.Select(v => $"{v}"));
2:before: V2 { Value = 1 },V2 { Value = 2 },V2 { Value = 3 },V2 { Value = 4 },V2 { Value = 5 }
2:after : V2 { Value = 🐁 },V2 { Value = 🐄 },V2 { Value = 3 },V2 { Value = 🐈 },V2 { Value = 🐒 }

Index でいうところの
0: 反映される(インデクサから直接プロパティ変更)
1: 反映される(値渡し+プロパティ変更)
2: 反映されない(値渡し+インスタンス変更)
3: 反映される(参照渡し+プロパティ変更)
4: 反映される(参照渡し+インスタンス変更)
となります。

参照型(不変)

例えばこういったクラス(参照型)があったとするじゃないですか。

record class V3(string Value);

C# でいう record class は不変な クラスレコードです。

次の様な操作をするとどうなるでしょうか?

V3[] array = [new("1"), new("2"), new("3"), new("4"), new("5")];
Console.WriteLine($"3:before: {ToString(array)}");
int i = 0;
{
    //0: [直接編集不可] 配列のインデクサは参照戻り値
    i++;
    // array[i++].Value = "🐁";
}
{
    // 代入(値渡し)
    {
        var v = array[i++];
        //1: [直接編集不可]プロパティを変更
        // v.Value = "🐄";
    }
    {
        var v = array[i++];
        //2: インスタンスを更新
        v = v with {
            Value = "🐅",
        };
    }
}
{
    // ref ローカル変数 (参照渡し)
    {
        ref var v = ref array[i++];
        //3: [直接編集不可] プロパティを変更
        // v.Value = "🐈";
    }
    {
        ref var v = ref array[i++];
        //4: インスタンスを更新
        v = v with {
            Value = "🐒",
        };
    }
}
Console.WriteLine($"3:after : {ToString(array)}");
string ToString<T>(T[] array) => string.Join(",", array.Select(v => $"{v}"));
3:before: V3 { Value = 1 },V3 { Value = 2 },V3 { Value = 3 },V3 { Value = 4 },V3 { Value = 5 }
3:after : V3 { Value = 1 },V3 { Value = 2 },V3 { Value = 3 },V3 { Value = 4 },V3 { Value = 🐒 }

Index でいうところの
0: 許されない(インデクサから直接プロパティ変更)
1: 許されない(値渡し+プロパティ変更)
2: 反映されない(値渡し+インスタンス変更)
3: 許されない(参照渡し+プロパティ変更)
4: 反映される(参照渡し+インスタンス変更)
となります。

まとめ

つまり表にまとめるとこう

項目 値型+可変 参照型+可変 参照型+不変
インデクサから直接プロパティ変更 -
値渡し+プロパティ変更 -
値渡し+インスタンス変更
参照渡し+プロパティ変更 -
参照渡し+インスタンス変更

上記の動作から見て、 値型の プロパティの変更も インスタンスの変更も同様の挙動とみて良さそうです。
また、インデクサからの直接変更は 参照渡し による変更に間違いは無さそうです。

参照型+不変とすることで 参照型の特性である インスタンスを変更せずにプロパティの変更ができなくなり、値型の挙動と同じになることが確認できました。

値渡ししかない言語であればまず違いはわからないですね。

以上。

使ったソース

sharplab

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?