我有一个asp.net程序,可以创建一个简单的调查表供用户回答。 大多数问题都使用下拉列表,答案分数为1-5(不好),我试图将事件处理程序添加到下拉列表对象中,这样只有当用户选择两者之间的分数时才会启用评论框。 1和2。
然而,当我为事件处理程序添加委托lambda调用时,而不是每个下拉列表影响他们自己的相应注释框,它们似乎都只指向添加的最后一个(并且它们工作一次,然后不再工作,只有最后一个ddl继续有预期的行为。)
我的代码:
//Called from Page_Load
private void PopulateSurvey()
{
btnSubmit.Enabled = true;
List<Question> questions = (from p in context.Questions
join q in context.Survey_Questions on p.ID equals q.QuestionID
where q.SurveyID == surveyid
select p).ToList();
Table tbl = new Table();
tbl.Width = Unit.Percentage(100);
TableRow tr;
TableCell tc;
TableCell tc1;
TableCell tc2;
TextBox txt;
CheckBox cbk;
DropDownList ddl = new DropDownList();
foreach (Question q in questions)
{
if (q.Division.Equals("General") || q.Division.Equals(ddlDivisions.SelectedValue.ToString()))
{
tr = new TableRow();
tc = new TableCell();
tc.Width = Unit.Percentage(55);
tc.Text = q.Text;
tc.Attributes.Add("id", q.ID.ToString());
tr.Cells.Add(tc);
tc = new TableCell();
if (q.QuestionType.ToLower() == "singlelinetextbox")
{
txt = new TextBox();
txt.ID = "txt_" + q.ID;
//txt.Width = Unit.Percentage(40);
tc.Controls.Add(txt);
}
if (q.QuestionType.ToLower() == "multilinetextbox")
{
txt = new TextBox();
txt.ID = "txt_" + q.ID;
txt.TextMode = TextBoxMode.MultiLine;
//txt.Width = Unit.Percentage(40);
tc.Controls.Add(txt);
}
if (q.QuestionType.ToLower() == "singleselect")
{
ddl = new DropDownList();
ddl.ID = "ddl_" + q.ID;
//ddl.Width = Unit.Percentage(41);
if (!string.IsNullOrEmpty(q.Options))
{
string[] values = q.Options.Split(',');
foreach (string v in values)
ddl.Items.Add(v.Trim());
}
ddl.AutoPostBack = true;
tc.Controls.Add(ddl);
}
//tc.Width = Unit.Percentage(60);
tr.Cells.Add(tc);
//Add comment row
tc1 = new TableCell();
//tc.Width = Unit.Percentage(5);
tc1.Text = "Comentario: ";
tc1.Attributes.Add("id", q.ID.ToString());
//tc1.Visible = false;
tr.Cells.Add(tc1);
tc2 = new TableCell();
txt = new TextBox();
txt.ID = "txt_" + q.ID + "comment";
txt.TextMode = TextBoxMode.SingleLine;
//txt.Width = Unit.Percentage(25);
tc2.Controls.Add(txt);
tr.Cells.Add(tc2);
ddl.SelectedIndexChanged+= (sender, e) => ScoreChanged(sender, e, tc1,tc2, ddl.SelectedIndex);
tbl.Rows.Add(tr);
}
}
pnlSurvey.Controls.Add(tbl);
}
protected void ScoreChanged (object sender, EventArgs e, TableCell tc1, TableCell tc2, int score)
{
if( score <2)
{
tc1.Visible = false;
tc2.Visible = false;
}
else
{
tc1.Visible = true;
tc2.Visible = true;
}
}
首先想到的是,事件处理程序可能会保留引用本身,而不是每个问题创建的孤立对象,因此所有事件处理程序最终都使用相同的tc1和tc2引用,因此只有最后一个对象?是这样的,如果是这样,我该怎么回事呢?
答案 0 :(得分:0)
你的问题在这一行:
ddl.SelectedIndexChanged +=
(sender, e) => ScoreChanged(sender, e, tc1,tc2, ddl.SelectedIndex);
这里有两件事很重要:
ScoreChanged
。tc1
和tc2
作为参数传递给ScoreChanged
方法。您已在代码块的开头定义了这些变量,外部循环。这个上下文中的神奇词是闭包。由于tc1
和tc2
的定义超出了匿名方法的范围,因此它们会变成闭包。这意味着在定义方法时不会有值,但在调用时。由于您不断地在foreach
循环中覆盖变量的值,因此在调用时,这些变量将具有循环的最后一次迭代的值。
解决方案很简单:在循环中声明变量 。这将为foreach
的每次迭代创建一个新的闭包:
TableRow tr;
TableCell tc;
TextBox txt;
CheckBox cbk;
foreach (Question q in questions)
{
TableCell tc1;
TableCell tc2;
DropDownList ddl; //Don't forget to include ddl, since you are using its selected index
//...
更一般地说:不要在C#中的方法开头做这样的“声明”块。在第一次使用它时声明变量(除非有充分的理由不这样做,例如你希望它成为闭包的一部分)。有很多很好的理由,你只是经历过其中一个。另一个是当您将代码的一部分转换为具有Visual Studio重构功能的方法时,您将把预先声明的变量作为ref
参数传递。这些是最明显的原因。还有更多。