事件处理程序和其他回调方法通常仅通过委托机制进行调用,而从不直接进行调用。所以,我们目前还是只能将事件处理程序代码及回调代码置于特定的方法中,并显式为方法创建委托。而匿名方法 (anonymous method) 则不同,它允许与委托关联的代码以“内联”方式写入使用委托的位置,从而方便地将代码直接“绑定”到委托实例。除了这种便利之外,匿名方法还能够对包含它的函数成员的局部状态进行共享访问。而要使用具名方法实现同样的状态共享,需要将局部变量“提升”到手动编写的辅助类实例的字段中。
下面的示例显示一个包含列表框、文本框和按钮的简单输入窗体。单击该按钮时,一个包含文本框中所示文本的项将被添加到列表框中。
class InputForm: Form
{
ListBox listBox;
TextBox textBox;
Button addButton;
public MyForm() {
listBox = new ListBox(...);
textBox = new TextBox(...);
addButton = new Button(...);
addButton.Click += new EventHandler(AddClick);
}
void AddClick(object sender, EventArgs e) {
listBox.Items.Add(textBox.Text);
}
}
虽然在响应该按钮的 Click 事件时只执行了一条语句,但是必须将该语句提取到一个具有完整参数列表的单独方法中,并且必须手动创建一个引用该方法的 EventHandler 委托。使用匿名方法,该事件处理代码明显变得更简洁了:
class InputForm: Form
{
ListBox listBox;
TextBox textBox;
Button addButton;
public MyForm() {
listBox = new ListBox(...);
textBox = new TextBox(...);
addButton = new Button(...);
addButton.Click += delegate {
listBox.Items.Add(textBox.Text);
};
}
}
匿名方法由关键字 delegate、可选的参数列表和包含在 { 和 } 分隔符中的语句列表组成。上述示例中的匿名方法没有使用委托提供的参数,因此可以省略参数列表。若要获得对参数的访问,该匿名方法可以包括参数列表:
addButton.Click += delegate(object sender, EventArgs e) {
MessageBox.Show(((Button)sender).Text);
};
在上面的示例中,发生了从该匿名方法到 EventHandler 委托类型(Click 事件的类型)的隐式转换。能够进行该隐式转换是因为委托类型的参数列表和返回类型与匿名方法兼容。具体的兼容规则如下:
· 如果下列条件之一成立,则委托的参数列表与匿名方法兼容:
19. 匿名方法没有参数列表并且委托没有 out 参数。
20. 匿名方法包含的参数列表与委托的参数列表在数目、类型和修饰符方面都精确匹配。
· 如果下列条件之一成立,则委托的返回类型与匿名方法兼容:
21. 委托的返回类型为 void,并且匿名方法没有 return 语句或只有无表达式的 return 语句。
22. 委托的返回类型不为 void,并且与匿名方法中的所有 return 语句关联的表达式都可隐式转换为委托的返回类型。
委托的参数列表和返回类型都必须与匿名方法兼容才能进行从匿名方法到委托类型的隐式转换。
下面的示例使用匿名方法编写“内联”函数。匿名方法作为 Function 委托类型的参数来传递。
using System;
delegate double Function(double x);
class Test
{
static double[] Apply(double[] a, Function f) {
double[] result = new double[a.Length];
for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
return result;
}
static double[] MultiplyAllBy(double[] a, double factor) {
return Apply(a, delegate(double x) { return x * factor; });
}
static void Main() {
double[] a = {0.0, 0.5, 1.0};
double[] squares = Apply(a, delegate(double x) { return x * x; });
double[] doubles = MultiplyAllBy(a, 2.0);
}
}
Apply 方法将给定的 Function 应用于 double[] 的各元素,并返回含有结果的 double[]。在 Main 方法中,传递给 Apply 的第二个参数是与 Function 委托类型兼容的匿名方法。此匿名方法完成一个简单的操作,即返回其实参的平方,因此该 Apply 调用的结果是包含 a 中各个值的平方的 double[]。
MultiplyAllBy 方法返回一个 double[],其中的值分别为实参数组 a 中的每个值乘以给定的 factor 后所得的结果。为了获得所需的结果,MultiplyAllBy 调用了 Apply 方法,并传递一个用来将实参 x 乘以 factor 的匿名方法。
其作用域包含某匿名方法的局部变量和参数称为该匿名方法的外层变量 (outer variable)。在 MultiplyAllBy 方法中,a 和 factor 是传递给 Apply 的匿名方法的外层变量,并且由于该匿名方法引用 factor,我们称 factor 被该匿名方法捕获 (captured)。通常,局部变量的生存期仅限于该变量所关联的代码块或语句的执行期间。但是,被捕获的外层变量的生存期将至少延长至引用匿名方法的委托可以被垃圾回收为止。
22.1.1 方法组转换
正如前一节所述,匿名方法可隐式转换为与之兼容的委托类型。C# 2.0 允许对方法组进行这种相同类型的转换,这样,几乎在所有情况下都可以省略显式的委托实例化。例如,语句
addButton.Click += new EventHandler(AddClick);
Apply(a, new Function(Math.Sin));
可改写为
addButton.Click += AddClick;
Apply(a, Math.Sin);
当使用较短的形式时,编译器自动推断要实例化的委托类型,而效果与使用较长的形式是一样的。