Я не знаю, как объяснить проблему именно в слове. Я написал код, чтобы воспроизвести проблему, и я изо всех сил старался сделать код легким для чтения.
delegate string del();
static void Main(string[] args)
{
string[] ss = { "a", "b" };
Dictionary<string, del> dic = new Dictionary<string, delSS>();
foreach (string s in ss) {
dic[s] = () => s;
}
foreach (string s in ss) {
System.Console.WriteLine("{0}:{1}", s, dic[s]());
}
System.Console.ReadLine();
}
Я создал два выражения лямбда для "a" и "b", для "a" он возвращает "a", а для "b" он возвращает "b". Делегаты сохраняются в словаре с именем dic. И я называю их по-разному. Ожидаемый результат
a:a
b:b
и это результат VS2013. Но в VS2008 результат
a:b
b:b
Я пробовал версии.NET Framework от 3.0 до 4.5 в VS2013, и все они дают тот же результат. Я также пробовал 3.0 и 3.5 в VS2008, и они тоже дают одинаковые результаты. Я думал, это означает, что это не ошибка в Framework, а ошибка компилятора. Я разрабатываю приложение для WinCE 6.0, которое поддерживается только в VS2008 и более ранних версиях. Вот почему я должен использовать VS2008. Я нашел проблему, когда я отлаживаю приложение WinCE. Я думал, что это ошибка Compact Framework, но это оказалось ошибкой VS2008. Кто-нибудь знает, как это исправить?
Захват переменной цикла внутри замыкания является сложным. Приятно, что VS2013 сортирует его для вас, но я не думаю, что это безопасно использовать вообще.
Джон Скит объясняет это здесь (и в своих книгах): Захваченная переменная в цикле на С#. Инструменты качества кода, такие как Resharper, вызывают предупреждение в любое время, когда вы пытаетесь это сделать.
Ну компилятор стал лучше, я это даю;)
Пытаться
foreach (string s in ss) {
var closed = s;
dic[s] = () => closed;
}
Это создаст новую переменную/значение для каждого s
чтобы закрытие не указывало на ваши s
которые будут мутироваться циклом foreach
до конца.
Вот решение. Я сделал следующую модификацию, и я попробовал ее в vs2008, он отлично выполняет делегирование строки del (string sr);
static void Main( )
{
string[] ss = { "a", "b" };
Dictionary<string, del> dic = new Dictionary<string, del >();
foreach (string s in ss)
{
dic[s] = (sr) => sr;
}
foreach (string s in ss)
{
System.Console.WriteLine("{0}:{1}", s, dic[s].Invoke(s));
}
System.Console.ReadLine();
}
Тот факт, что вы используете делегат, подразумевает, что вы должны использовать преимущество того факта, что вы можете передать параметр в делегат. Когда вы помещаете переменную в функцию лямбда, это делает CLR, чтобы выбрать последнюю позицию указателя цикла foreach как переменной s для всех.