2012-02-09 144 views
2

我想对Button_Click事件将值传递给动态按钮的事件处理程序

public MyClass() 
{ 
    Int64 po = 123456; 
    foreach (Expense expense in pr.Expenses) 
    { 
     Button btnExpenseDetail = new Button(); 
     btnExpenseDetail.Text = expense.ExpenseName; 
     btnExpenseDetail.Location = new Point(startLocation.X + 410, startLocation.Y + (23 * 
     btnExpenseDetail.Click += (sender, e) => { MyHandler(sender, e, po , expense.ExpenseName); };    
     pnlProjectSummary_Expenses.Controls.Add(btnExpenseDetail); 
    } 
} 


void MyHandler(object sender, EventArgs e, string po, string category) 
{ 
    FormExpenseDetails ed = new FormExpenseDetails(po, category); 
    ed.Show(); 
} 

我使用Visual Studio 2010的C#经过两个值。在面板上,每个按钮的文本值都是不同的。但按钮的Click_Events行为完全相同。有人能告诉我哪一部分代码我得到这个逻辑错误吗?

============================================== ==========================

+0

请不要用“C#”和这样的前缀您的图书。这就是标签的用途。 – 2012-02-09 08:14:52

回答

5

看起来像统计员的一个常见问题。基本上,如果您为lambda使用枚举变量(在这种情况下为expense),它总是在同一个变量上创建闭包,所以它总是使用相同的值。你能解决这个问题是这样的:

foreach (Expense expense in pr.Expenses) 
{ 
    var currentExpense = expense; // <-- This should help. Also use this variable for the lambda. 
    Button btnExpenseDetail = new Button(); 
    btnExpenseDetail.Text = currentExpense .ExpenseName; 
    btnExpenseDetail.Location = new Point(startLocation.X + 410, startLocation.Y + (23 * 
    btnExpenseDetail.Click += (sender, e) => { MyHandler(sender, e, po , currentExpense.ExpenseName); };    
    pnlProjectSummary_Expenses.Controls.Add(btnExpenseDetail); 
} 

你可以把你的拉姆达为传递一个参考变量expense。即使与每次迭代变量的值发生变化,参考仍然指向相同的变量。这就是为什么它帮助创建每次迭代(currentExpense)一个本地变量。字符串值和位置也不同,因为它们在每次迭代中被分配到另一个位置(Button.Text,Button.Location)。

+0

完美的作品!也感谢您的详细和全面的解释。 – 2012-02-09 09:44:01

5

此代码应工作:

public MyClass() 
{ 
    Int64 po = 123456; 
    foreach (Expense expense in pr.Expenses) 
    { 
     var expenseName = expense.ExpenseName; 
     Button btnExpenseDetail = new Button(); 
     btnExpenseDetail.Text = expense.ExpenseName; 
     btnExpenseDetail.Location = new Point(startLocation.X + 410, startLocation.Y + (23 * 
     btnExpenseDetail.Click += (sender, e) => { MyHandler(sender, e, po, expenseName); };    
     pnlProjectSummary_Expenses.Controls.Add(btnExpenseDetail); 
    } 
} 


void MyHandler(object sender, EventArgs e, string po, string category) 
{ 
    FormExpenseDetails ed = new FormExpenseDetails(po, category); 
    ed.Show(); 
} 

我们去了一些更基本。

static void Main(string[] args) 
{ 
    var qs = new List<Action>(); 

    for (var i = 0; i < 10; i++) 
     qs.Add(() => f("doer", i)); 

    for (var i = 0; i < 10; i++) 
     qs[i](); 

} 

private static void f(string x, int y) 
{ 
    Console.WriteLine("{0}: {1}", x, y); 
} 

当你运行上面的代码,你永远得到的输出为:“实干家:10”。让反编译代码:

private static void f(string x, int y) 
{ 
    Console.WriteLine("{0}: {1}", x, y); 
} 

private static void Main(string[] args) 
{ 
    List<Action> qs = new List<Action>(); 
    <>c__DisplayClass1 CS$<>8__locals2 = new <>c__DisplayClass1(); 
    CS$<>8__locals2.i = 0; 
    while (CS$<>8__locals2.i < 10) 
    { 
     qs.Add(new Action(CS$<>8__locals2.<Main>b__0)); 
     CS$<>8__locals2.i++; 
    } 
    for (int i = 0; i < 10; i++) 
    { 
     qs[i](); 
    } 
} 

[CompilerGenerated] 
private sealed class <>c__DisplayClass1 
{ 
    // Fields 
    public int i; 

    // Methods 
    public void <Main>b__0() 
    { 
     Program.f("doer", this.i); 
    } 
} 

正如你所看到的,是编译器生成一个名为c__DisplayClass1类,并进入循环前初始化一次。在此之后,它只是增加了可变CS$<>8__locals2i财产。

因此,当我在下一个循环调用这些lambda时,它使用CS$<>8__locals2对象来查看内部变量。

(我的英文不好enaugh来解释它,但是这一切都没有...)

1

这与C#< = 4如何处理foreach循环有关。基本上,实例开销是在循环外部定义的,然后是一个内部循环,它将指针更改为下一个项目。像这样的伪代码:

Expense expense; 
for expense in pr.Expenses 
    // do processing 

如果您认为在引用方面,引用的价值,费用指向,迭代期间的变化。所以,当点击事件触发时,它指向最后一个项目。现在,这应该在c#5中得到修复,这里已经有了一个讨论。

修复的方法是相当简单:

Int64 po = 123456; 
foreach (Expense expense in pr.Expenses) 
{ 
    var localExpense = expense; 
    Button btnExpenseDetail = new Button(); 
    btnExpenseDetail.Text = expense.ExpenseName; 
    btnExpenseDetail.Location = new Point(startLocation.X + 410, startLocation.Y + (23 * 
    btnExpenseDetail.Click += (sender, e) => { MyHandler(sender, e, po , localExpense.ExpenseName); };    
    pnlProjectSummary_Expenses.Controls.Add(btnExpenseDetail); 
} 
相关问题