#c_sharp #c_sharp_faq
Определяю класс-издатель события class Car { public string Name { get; set; } public Car(string name) { Name = name; } public event EventHandler Started; public void Start() { if (Started != null) Started(this, EventArgs.Empty); } } , класс-подписчик class Driver { public string Name { get; set; } public Driver(string name) { Name = name; } } , тестирую static void Main(string[] args) { var fomenko = new Driver("Фоменко"); var shumaher = new Driver("Шумахер"); var vasya = new Driver("Вася"); Driver[] drivers = new Driver[] { fomenko, shumaher, vasya }; Listcars = new List (); foreach (var driver in drivers) { var car = new Car ( driver == fomenko ? "Маруся" : driver == shumaher ? "Ф1" : "Запорожец" ); car.Started += delegate(object o, EventArgs ea) { Console.WriteLine("Стартовала машина {0} с пилотом {1}", car.Name, driver.Name); }; cars.Add(car); } foreach (var car in cars) { car.Start(); } Console.ReadKey(); } Выдаёт Стартовала машина Маруся с пилотом Вася Стартовала машина Ф1 с пилотом Вася Стартовала машина Запорожец с пилотом Вася А хотелось бы Стартовала машина Маруся с пилотом Фоменко Стартовала машина Ф1 с пилотом Шумахер Стартовала машина Запорожец с пилотом Вася Почему так происходит? Как исправить? Версия C# 3.0 .Net 3.5
Ответы
Ответ 1
Это - особенность старой версии языка. Переменная цикла захватывается по ссылке, а не по значению - и потому к моменту возникновения события указывает на последнего водителя. Решений тут три: Если возможно, перейдите на современную версию языка. Это проще, чем вы думаете: Visual Studio Community Edition бесплатна для любого личного использования, обучения или работы над открытыми проектами. Скопируйте значение переменной цикла в локальную переменную: foreach (var driver in ...) { var driver2 = driver; //... } Смените тип коллекции drivers с массива на список (List) - тогда вы получите метод ForEach, принимающий делегат: drivers.ForEach(driver => { //... }); Ответ 2
Небольшое уточнение/дополнение к правильному ответу @Pavel Mayorov. В C# до 5-ой версии цикл foreach (var driver in drivers) { // тело цикла } раскрывался примерно в такую конструкцию: using (var en = drivers.GetEnumerator()) { Driver driver; // вне цикла while (en.MoveNext()) { driver = en.Current; // тело цикла } } Поэтому все замыкания видели одну и ту же переменную driver, которая менялась с каждой итерацией цикла. И значит, при приходе события Started у этой переменной было уже «финальное» значение. Начиная с версии 5.0, цикла стал раскрываться в другую конструкцию: using (var en = drivers.GetEnumerator()) { while (en.MoveNext()) { Driver driver = en.Current; // внутри цикла // тело цикла } } И значит, каждое замыкание теперь видит свою переменную driver, только для этой итерации. Таким образом, в C# 5+ ваш код будет вести себя ожидаемым образом. Дополнительное чтение по теме: Closing over the loop variable considered harmful.Ответ 3
Ваш delegate захватывает переменную driver (это именно closure), а когда машины стартуют, то в ней находится последний в списке водитель, он же Вася. Как уже посоветовали, попробуйте "внутри форича скопипастить водителя в переменную" (лучше прямо driver.Name) и посмотрите на результат. Правильнее всего, конечно, в Car иметь ссылку на Driver
Комментариев нет:
Отправить комментарий