【Unity勢に】C#のforeachとラムダ式の落とし穴、そしてその破壊的言語仕様変更【今読んでほしい】

# はじめに

　[C#アドベントカレンダー2016](http://qiita.com/advent-calendar/2016/csharp)、19日目は『【Unity勢に】C#のforeachとラムダ式の落とし穴、そしてその破壊的言語仕様変更【今読んでほしい】』と題して室星が担当させていただきます。

　タイトルはラムダ式としていますが、正確にはデリゲートです。

# foreachとデリゲートの落とし穴

　次のコードを実行するとどのようなログが出力されるでしょうか？

```csharp
var names = new List<string> { "Taro", "Jiro", "Saburo" };
var actions = new List<Action>();

foreach (string name in names)
{
    actions.Add(() =>
    {
         Console.WriteLine(name);
    });
}

foreach(var action in actions)
{
    action();
}
```

　ラムダ式を用いて`names`の要素を表示する`Action`型のデリゲートを生成し、それを`actions`というリストに追加しています。そして、`actions`の要素を列挙しデリゲートを実行しています。

　さぁ、実行結果はどうなるでしょうか？

　なんと結果はC#のバージョンなど、実行環境によって異なります！

　C# 5.0もしくはそれ以降で実行すると[^1]、次のようにTaro、Jiro、Saburoと順に表示されます。「こう表示されるのではないか？」と予想した方も多いのではないでしょうか？

```
Taro
Jiro
Saburo
```

　ところがC# 5.0以前[^1]で実行すると、先ほどのコードは次のような結果になります。

```
Saburo
Saburo
Saburo
```

　どういうことでしょうか？

# C# 5.0以前とforeachとデリゲート

```
Saburo
Saburo
Saburo
```

　という結果が予測と違った方もいたのではないでしょうか？

　C# 5.0以前[^1]では先ほどのコードのように、foreachで列挙した変数をデリゲートの中で利用した場合、最後に列挙した要素が常に使われてしまいます。

　そのため、TaroやJiroではなくSaburoが表示されてしまうのです。

　もし、C# 5.0以前[^1]で次のような結果にしたいのであれば、

```
Taro
Jiro
Saburo
```

　コードを次のように変更する必要があります。

```csharp
var names = new List<string> { "Taro", "Jiro", "Saburo" };
var actions = new List<Action>();

foreach (string name in names)
{
    string targetName = name; // ここがポイント
    actions.Add(() =>
    {
         Console.WriteLine(targetName);
    });
}

foreach(var action in actions)
{
    action();
}
```

　foreachで列挙した要素`name`を、foreachの中で宣言した変数`targetName`に代入しています。ここがポイントです。

# C# 5.0での破壊的な仕様変更

　foreachとデリゲートのコードを再掲します。

```csharp
var names = new List<string> { "Taro", "Jiro", "Saburo" };
var actions = new List<Action>();

foreach (string name in names)
{
    actions.Add(() =>
    {
         Console.WriteLine(name);
    });
}

foreach(var action in actions)
{
    action();
}
```

　繰り返しになりますが、このコードはC# 5.0で挙動が変わりました。C# 5.0とそれ以降では、

```
Taro
Jiro
Saburo
```

　と表示されます。

　この変更は破壊的な変更であることに注意してください。（元の挙動に依存したコードってかなりあれですが。。。）

　このことに関しては、[@ufcpp](https://twitter.com/ufcpp)さんの「[C# 5.0 の新機能](http://ufcpp.net/study/csharp/ap_ver5.html)」により詳しい説明があるので、さらに詳しく理解したい方はそちらも読まれるのをお勧めします。

# Unityクラスタにこそ、今知ってほしい！

　原稿執筆時(2016年12月中旬)、C# 7.0がそろそろ出そうな時期になぜ私がこんなC# 5.0関連の記事を書いたのか。

　それはゲームエンジンの方のUnityが関連します。（タイトルの通りこの記事は、UnityでC#を書いている人向けに書かせていただきました。）

　実は2016年の11月末にリリースされたUnity 5.5で、コンパイラのアップデートにより、このforeachとデリゲートの挙動の変更が適用されました。

　Unity 5.5以前は、

```
Saburo
Saburo
Saburo
```

　と表示されていたものが、Unity 5.5からは

```
Taro
Jiro
Saburo
```

　と表示されます。

　背景と流れをすこし説明します。

　Unityでは、今まで古いC# (原則 C# 3.0で一部 C# 4.0の機能も使える)が使えていました。

　2016年4月にUnityが.NET Foundationに入るなど、もろもろ様々な動きがありUnityで使えるC#のアップデートが決まりました。

　原稿執筆時はまだ製品版でC# 6.0はまだ使うことはできません。（[Experimental Preview](https://forum.unity3d.com/forums/experimental-scripting-previews.107/)版でC# 6.0を試すことは可能です）

　しかし、コンパイラのアップデートは製品版でもすでに始まっていて、C# 5.0で行われたforeachとデリゲートの破壊的な言語仕様変更は、Unity 5.5ではすでに行われているようです。

　繰り返しになりますが、次のコードはUnity 5.5の前と後で挙動が異なります。特にUnity 5.5以前の挙動は期待した挙動とことなり、思わぬバグの原因になりうるという点に注意してください！


```csharp
var names = new List<string> { "Taro", "Jiro", "Saburo" };
var actions = new List<Action>();

foreach (string name in names)
{
    actions.Add(() =>
    {
         Console.WriteLine(name);
    });
}

foreach(var action in actions)
{
    action();
}
```

# さいごに

　UnityのAsset製作者さんは、古いUnityも対象に含めていることが多いと思いますので、Unity 5.5とそれ以前で挙動が異なる点に注意してください！

　コンパイラは始まったアップデート、Unityでも早くC# 6.0が使えるといいですね！


[^1]: Unityでは、Unity 5.5以前は変更前挙動に、Unity 5.5とそれ以降は変更後の挙動となります。