我正在尝试在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#。在面板上,每个Button的文本值都是不同的。但按钮'Click_Events的行为完全相同。有人能告诉我哪部分代码我得到了这个逻辑错误吗?
=============================================== =========================
答案 0 :(得分:6)
看起来像是普查员的常见陷阱。基本上,如果对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);
}
您可以将lambda视为传递对变量expense
的引用。即使变量的值随每次迭代而变化,引用仍指向同一变量。这就是为什么它有助于为每次迭代(currentExpense
)创建一个本地范围的变量。字符串值以及位置不同,因为每次迭代都会将它们分配给另一个位置(Button.Text
,Button.Location
。
答案 1 :(得分: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);
}
当你运行上面的代码时,你总是得到输出:“doer: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
的类,并在进入循环之前对其进行了初始化。之后,它只增加了变量i
的{{1}}属性。
因此,当我在下一个循环中调用theese lambdas时,它使用CS$<>8__locals2
对象来查看内部变量。
(我的英语不是很好解释它,但它就在那里......)
答案 2 :(得分:1)
这与C#&lt; = 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);
}