foreach でコレクションに格納しているstringが更新できない のは string が 不変性を持っているからだけではない

:::note info
この記事は [`foreachでコレクションに格納しているStringは更新できない/不変性`](https://qiita.com/Rui_K/items/bbbcea518a7ac8d958ab#comment-be8024911240fd388e90) に関する 記事となっております。
:::

元記事では主に java であったが、 C# だと次の様に書ける。

```cs
using System;

string[] strArray = [ "a", "b", "c" ];
// foreachで更新。
foreach (var str in strArray) {
    str += "X";
    Console.WriteLine($"01:{str}");
}
// 配列の内容出力
foreach(var str in strArray) {
    Console.WriteLine($"02:{str}");
}
```

こう実行したとき 出力結果が次の様になる。

```text
error CS1656: Cannot assign to 'str' because it is a 'foreach iteration variable'
```

そう、ビルドエラー [`CS1656`](https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/compiler-messages/cs1656) である。

これは C# に於いて、 foreach 時の取り出された要素への再代入は意味が無い為、禁止されているからである。

何故意味が無いかを解説する為に一度 for で書いてみるならば次の様に書ける。

```cs
using System;

string[] strArray = [ "a", "b", "c" ];
for(var i = 0;i<strArray.Length; i++) {
    // ここで参照のコピーが渡される
    var str = strArray[i];
    // ここで上書きされるのは `str` 変数であって strArray[i] ではない
    str += "X";
    Console.WriteLine($"01:{str}");
}
// 配列の内容出力
foreach(var str in strArray) {
    Console.WriteLine($"02:{str}");
}
```

<details>
<summary>出力結果</summary>
 
[sharpLab](https://sharplab.io/#v2:C4LgTgrgdgPgAgJgIwFgBQ65IAwG0C6ABAM7BgCCYYAhgJ6EC8huhARNawDRsBGXbAY1aF8AbnQAzAPZgAFADdqYQgEtGhbKJUAeUhSp0AdABkAplADmwABajVAansBKQgG90hT4QD03woGUGf0BzBkAh5UBzR0A7BkBmhkAVhkAfhkAZBkBCO0BVBkAYhkBohg8vRWU9dT1KGlpcFTFsz18A4MAoOUAPs0BZBnSMiMB7BkIAAz0OwkBITUAHUyDAIQZAYwZAMwYSMiK6UqIg1sArBkARBgrJ5XsmVgANVnE0L0IsAE5ZABJWbCQQVz0AX1YnPdv0KsBZRMB0JQjAUMVATu1AL8VANlKkhkpmoAmsCiUa1UUDW01oLnc+y8xzOFwQ1zuDyeQA===)
 
```text
01:aX
01:bX
01:cX
02:a
02:b
02:c
```
 
</details>


もしも 変数に参照のコピーではなく、参照をそのまま使いたいのであれば[`参照変数`(もしくは `ref ローカル`)](https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/statements/declarations#reference-variables)を用いて次の様に書けばよい。

```cs
using System;

string[] strArray = [ "a", "b", "c" ];
for(var i = 0;i<strArray.Length; i++) {
    // ここで参照が渡される
    ref var str = ref strArray[i];
    // ここで上書きされるのは 参照先の strArray[i]
    str += "X";
    Console.WriteLine($"01:{str}");
}
// 配列の内容出力
foreach(var str in strArray) {
    Console.WriteLine($"02:{str}");
}
```

<details>
<summary>出力結果</summary>
 
[sharpLab](https://sharplab.io/#v2:C4LgTgrgdgPgAgJgIwFgBQ65IAwG0C6ABAM7BgCCYYAhgJ6EC8huhARNawDRsBGXbAY1aF8AbnQAzAPZgAFADdqYQgEtGhbKJUAeUhSp0AdABkAplADmwABajVAansBKQgG90hT4QD03woGUGf0BzBkAh5UBzR0AZBkBCO0BVBkAYhkBohg8vMFMJQkVlPXVU9L1KGlpcFTFkz18A4MAoOUAPs0BZBniEwDsGQHsGQnDACUUmkjICumL8Mp7leyZWAA1WcTQvQiwATlkAElZsJBBXPQBfVidprfQKwFlEwHQlJsBQxUBO7UAvxUBspUkZU2oBawUlYdUoYb7aF3cZrwLZarBAbba7fZAA===)
 
```text
01:aX
01:bX
01:cX
02:aX
02:bX
02:cX
```
 
</details>


これは 配列の インデクサアクセスが `ref T item[int index]` として実装されている為、可能なことである。
ただ、foreach の参照変数対応のインターフェスは 配列には用意されていない為(厳密には foreach を展開する際 ref 戻り値の特別対応ができない為)、 .NET Sta `Span<T>` を 用いる形にすれば可能である。（勿論、`IEnumerator<T>.Current` にあたる戻り値が ref T な 実装を持つ wrapper を追加で作れば対応できるのだが、一番手頃なのが `Span<T>` な為そちらで進める。（ [sharplabとかで変換結果見ればわかる](https://sharplab.io/#v2:D4AQTAjAsAUCAMACEEBsiBKBTAZgUQA8AXLAOwGcBLAewoG5ZYV4BtAXUXKICcBBb7gEMAnogC8iFogBEg6QBoZAIwUyAxtMRsGMAPS7EAZQAOg0gB4AKgD5EgCSdYOatyyC1ACwAULnIgBugtycPIiUpMF8AiIAdLzkJmaeAJRJiADesIhZyBAAnJ4AJNLwEABcaVzcAL7SSTpVsPqIgFeBgDryiD6IgNxGgFEMgCQKDjBOLm5enQFBlaHhlfxCwrHk2DjJqRkw2Tn5RfBg5ZU1dbANMIx6BoCYVoBpdoDmDB243f2A1gyAVgyAxgyAZgyAIgyA0QyIeKQAK4AWywQiUABssIhAPYMLy+gDsGG5tQBADLBKoC1ERMLg5iIASCwYJIVgrNZYOtNpZ2IgAPqBeY6TYgADMOJweOEBNB4KhnmpHAZIlSYls9KiogkQuETOyrPZnO5RKIzjJiAA4lgiEqhCruMlxLZSFgAO4KiU6wR6smecXzI4wE7oniY7HLRVAnlW1U2CmZbJhbG0sIAEywBFlWQFdOlkeQbPdFs9yuc/Jp0pFtltofDijtwvEiE8AFoIIoM3GmgBhQECMjYwAyDPdfL0+q9Pr8EQ3ALsMgBKGQDPDIAJhjRGzlbM6lkQNbrpGxoubMYlLGDpDDBG0/qy8qU1GoEMQAFlqH4sAA5cNEA3zgDU15Xa8Q5kX82iABkyABzIjuepnFDocB2UIEgKBoCg/VHLc2X/c15ktEk1TiZYbW/ShyEQaMM0NRBjTNRM4OTXlSRsTwK2OIA=) が `Span<T>` こそ特別対応されているので厳密には違うが、ここでは考えないことにする。

```cs
using System;

string[] strArray = [ "a", "b", "c" ];
foreach(ref var str in strArray.AsSpan()) {
    str += "X";
    Console.WriteLine($"01:{str}");
}
// 配列の内容出力
foreach(var str in strArray) {
    Console.WriteLine($"02:{str}");
}
```

<details>
<summary>出力結果</summary>

[sharpLab](https://sharplab.io/#v2:C4LgTgrgdgPgAgJgIwFgBQ65IAwG0C6ABAM7BgCCYYAhgJ6EC8huhARNawDRsBGXbAY1aF8AbnQAzAPZgAptQEALABRyJhAG7UwJMoQCWUXRSp0AdOWIBlAA7UoygJSPCAb3SFPxwgGomrAA1WcTQvQiwATmUAElZsJBBXUjAAX1ZHEJT0AHpswkBZRMB0JUA7BkBQxUBO7UAvxUBspUkZeSVlLR1kgyNkyhpaF3dQr0iYuIRE5LSM9BSgA=)

```text
01:aX
01:bX
01:cX
02:aX
02:bX
02:cX
```

</details>


ちなみに List には `AsSpan()` が生えていない為、することができないと思いきや、[構造は割れている](https://github.com/microsoft/referencesource/blob/51cf7850defa8a17d815b4700b67116e3fa283c2/mscorlib/system/collections/generic/list.cs#L40)ので 公式で [`CollectionsMarshal.AsSpan<T>(List<T>)`](https://learn.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices.collectionsmarshal.asspan) が用意されている。

つまり、上記のコードを元にするなら次の様なコードとなる。

```cs
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

List<string> strArray = [ "a", "b", "c" ];
foreach(ref var str in CollectionsMarshal.AsSpan(strArray)) {
    str += "X";
    Console.WriteLine($"01:{str}");
}
// 配列の内容出力
foreach(var str in strArray) {
    Console.WriteLine($"02:{str}");
}
```

<details>
<summary>出力結果</summary>

[sharpLab](https://sharplab.io/#v2:C4LgTgrgdgPgAgJgIwFgBQcAMACOSAsA3OlrkgHQBK0wAlgLYCm5AklMI2APYAOAypwButAMaMAzsTToAMrXHAAPHkwA+bArABBMGACGAT2wBebAG1sAIj2WANFYBGdqyMvYAulIBmXMIz0iABYAFH5e2IJ6YBrA0bRQ2ADCXAA2KYwidFxQ4gCyUeKBeinkWuJ8PHpQwZo6+gYAlA3YAN7o2B0x0QDUppYAGpZSnWQAnMEAJJaYSCAtmgC+lg1SC+gA9OvYgLKJgOhKgHYMgKGKgJ3agF+KgNlK6D5+ASGR0ZrY8V11hs1taCN441OYCHOLZarIA===)

```text
01:aX
01:bX
01:cX
02:aX
02:bX
02:cX
```

</details>

# 結論

`参照変数`を使えば参照型だろうが値型だろうが配列の要素を上書きができるので foreach でも使っていくことができる。その場合やりやすさ的に、 `Span<T>` に変換した方がよい。

以上。