2012-02-09 101 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); 
} 
相關問題