9

このスニペットの理由を説明できる人はいますか:

// Create required tasks
foreach (var messageToSend in messagesToSend)
{
  EmailMessage messageToBeSent = messageToSend;
  Task<bool> processingTask = new Task<bool>(() => SendMessage(messageToBeSent));
  processingTask.Start();
}

これとは異なる動作:

// Create required tasks
foreach (var messageToSend in messagesToSend)
{
  Task<bool> processingTask = new Task<bool>(() => SendMessage(messageToSend));
  processingTask.Start();
}

最初のスニペットでは、すべてのタスクが独自のメッセージで始まりますが、2 番目のスニペットでは、すべてのタスクが同じメッセージで始まります。

Resharper は次のように説明しています。なぜ異なる動作をすることがありますか?

4

2 に答える 2

16

Resharper は次のように説明しています。なぜ異なる動作をすることがありますか?

C# 4 と C# 5 の間には、特に C# 3 でラムダ式が導入されて以来、foreach のループ変数がクロージャによって影響を受ける方法が原因で、重大な変更がありました。Resharper は、依存している可能性がある場合などに備えて、これについて警告しています。以前のセマンティクスを期待するようになりました。

簡単に言えば、C# 4 では、ループ変数がループの各反復間で共有され、クロージャーが変数をキャプチャしたため、ほとんどの人がループ変数を閉じたときに予期しない結果をもたらしました。

C# 5 では、ループの反復ごとに独自の変数が取得されるため、1 つの反復のクロージャが他の反復と同じ変数で閉じられないため、(ほとんどの人にとって) より期待される結果が得られます。

それはあなたの問題の核心に私たちを導きます:

最初のスニペットでは、すべてのタスクが独自のメッセージで始まりますが、2 番目のスニペットでは、すべてのタスクが同じメッセージで始まります。

最初のスニペットでは、ループ内にループ変数のコピーを作成しており、内部変数に対してクロージャが発生しています。2 番目の例では、ループ変数を直接閉じます。おそらく、C# 4 で実行しているので、以前のセマンティクスが適用されます。C# 5 で実行している場合、両方のバージョンからのループ出力は一貫している必要があります。これは Resharper が言及する変更であり、C# 4 でコードを構造化する方法 (つまり、作成した最初のバージョンを使用する) も理解できるはずです。

Justin Pihony がコメントで指摘しているように、Eric Lippertは以前のセマンティクスに関する非常に役立つブログ記事を書き、C# 5 の変更についても言及しています。

于 2013-04-05T15:07:59.343 に答える
2

2 番目のコード サンプルには、すべてのラムダ式がクロージャでキャプチャする単一の があります。 messageToSend

デリゲートが実行されると、変数の現在の値が使用されます。

于 2013-04-05T15:07:55.367 に答える