Подтвердить что ты не робот

Делегаты обработчиков событий С# закрыты?

Я исхожу из функционального программирования на данный момент, так что простите меня, если я не понимаю закрытия в С#.

У меня есть следующий код для динамического генерации кнопок, которые получают анонимные обработчики событий:

for (int i = 0; i < 7; i++)
{
    Button newButton = new Button();

    newButton.Text = "Click me!";

    newButton.Click += delegate(Object sender, EventArgs e)
    {
        MessageBox.Show("I am button number " + i);
    };

    this.Controls.Add(newButton);
}

Я ожидал, что текст "I am button number " + i будет закрыт со значением i при этой итерации цикла for. Однако, когда я фактически запускаю программу, каждая кнопка говорит I am button number 7. Что мне не хватает? Я использую VS2005.

Изменить: Итак, я думаю, мой следующий вопрос: как мне получить значение?

4b9b3361

Ответ 1

Чтобы получить это поведение, вам нужно скопировать переменную локально, а не использовать итератор:

for (int i = 0; i < 7; i++)
{
    var inneri = i;
    Button newButton = new Button();
    newButton.Text = "Click me!";
    newButton.Click += delegate(Object sender, EventArgs e)
    {
        MessageBox.Show("I am button number " + inneri);
    };
    this.Controls.Add(newButton);
}

Обоснование обсуждается гораздо подробнее в этом вопросе.

Ответ 2

Ник это правильно, но я хотел объяснить немного лучше в тексте этого вопроса, почему именно.

Проблема не в закрытии; это за цикл. Цикл создает только одну переменную "i" для всего цикла. Он не создает новую переменную "i" для каждой итерации. Примечание. Сообщается, что это было изменено для С# 5.

Это означает, что когда ваш анонимный делегат захватывает или закрывает эту переменную "i" , она закрывает одну переменную, которая разделяется всеми кнопками. К тому времени, когда вы фактически нажмете любую из этих кнопок, цикл уже завершил увеличение этой переменной до 7.

Единственное, что я могу сделать иначе, чем код Nick, - использовать строку для внутренней переменной и строить все эти строки спереди, а не при нажатии кнопки, например:

for (int i = 0; i < 7; i++)
{
    var message = string.Format("I am button number {0}.", i);

    Button newButton = new Button();
    newButton.Text = "Click me!";
    newButton.Click += delegate(Object sender, EventArgs e)
    {
        MessageBox.Show(message);
    };
    this.Controls.Add(newButton);
}

Это просто обрабатывает немного памяти (держась за большие строковые переменные вместо целых чисел) за немного времени процессора в дальнейшем... это зависит от вашего приложения, что имеет значение больше.

Другой вариант - не вручную закодировать цикл вообще:

this.Controls.AddRange(Enumerable.Range(0,7).Select(i => 
{ 
    var b = new Button() {Text = "Click me!", Top = i * 20};
    b.Click += (s,e) => MessageBox.Show(string.Format("I am button number {0}.", i));
    return b;
}).ToArray());

Мне нравится этот последний вариант не так много, потому что он удаляет цикл, а потому, что он начинает думать о построении этих элементов управления из источника данных.

Ответ 3

Закрытие фиксирует переменную, а не значение. Это означает, что к моменту выполнения делегата, т.е. через некоторое время после окончания цикла, значение я равно 6.

Чтобы зафиксировать значение, назначьте его переменной, объявленной в теле цикла. На каждой итерации цикла будет создан новый экземпляр для каждой объявленной внутри него переменной.

Джон Скит статьи о закрытии имеет более глубокое объяснение и примеры.

for (int i = 0; i < 7; i++)
{
    var copy = i;

    Button newButton = new Button();

    newButton.Text = "Click me!";

    newButton.Click += delegate(Object sender, EventArgs e)
    {
        MessageBox.Show("I am button number " + copy);
    };

    this.Controls.Add(newButton);
}

Ответ 4

Вы создали семь делегатов, но каждый делегат содержит ссылку на тот же экземпляр i.

Функция MessageBox.Show вызывается только при нажатии кнопки. К моменту нажатия кнопки цикл завершается. Итак, на данный момент i будет равно семи.

Попробуйте следующее:

for (int i = 0; i < 7; i++) 
{ 

    Button newButton = new Button(); 

    newButton.Text = "Click me!"; 

    int iCopy = i; // There will be a new instance of this created each iteration
    newButton.Click += delegate(Object sender, EventArgs e) 
    { 
        MessageBox.Show("I am button number " + iCopy); 
    }; 

    this.Controls.Add(newButton); 
}

Ответ 5

К тому времени, как вы нажмете любую кнопку, все они были сгенерированы с 1 по 7, поэтому они все выражают конечное состояние i, которое равно 7.