2012-02-09 9 views
2

渡す値

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#」などを使用してタイトルを接頭辞ないでください。それがタグのためのものです。 –

答えて

5

は列挙子と共通の落とし穴のように見えます。あなたは、ラムダのために(この場合は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.TextButton.Location)に割り当てられているため、異なる値になります。

+0

完璧に動作します!また、詳細で包括的な説明をありがとう。 –

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という名前のクラスを生成し、ループに入る前に一度初期化されました。その後、変数CS$<>8__locals2iプロパティをインクリメントしただけです。

私は次のループにtheeseラムダを呼び出すときに、それは内部変数を見てCS$<>8__locals2オブジェクトを使用しています。

(私の英語は、それを説明するのは良いenaughではありませんが、それはすべてあります...)

1

これは、foreachループでどのようにC#< = 4取引に関係しています。基本的に、インスタンス費用はループの外で定義され、ポインタを次の項目に変更する内部ループがあります。この擬似コードのような何か:

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); 
}