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

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 ToStringT>(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 ToStringT>(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 ToStringT>(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



フラッグシティパートナーズ海外不動産投資セミナー 【DMM FX】入金

Source link