没有匿名方法的时候(C# 1.0)
addButton.Click += new EventHandler(AddClick);
void AddClick(object sender,EventArgs e)
{
listBox.Items.Add(textBox.Text);
}
有了匿名方法之后(C# 2.0)
addButton.Click += delegate
{
listBox.Items.Add(textBox.Text);
}
C# 支持用于调用一个或多个方法的委托 (delegate)。委托提供运算符和方法来添加或删除目标方法,它也可以在整个 .NET 框架中广泛地用于事件、回调、异步调用、多线程等。然而,仅仅为了使用一个委托,有时您不得不创建一个类或方法。在这种情况下,不需要多个目标,并且调用的代码通常相对较短而且简单。在 C# 2.0 中,匿名方法是一个新功能,它允许定义一个由委托调用的匿名(也就是没有名称的)方法。
例如,下面是一个常规 SomeMethod 方法的定义和委托调用:
class SomeClass { delegate void SomeDelegate(); public void InvokeMethod() { SomeDelegate del = new SomeDelegate(SomeMethod); del (); } void SomeMethod() { MessageBox.Show("Hello"); } |
}
可以用一个匿名方法定义和实现这个方法:
class SomeClass { delegate void SomeDelegate(); public void InvokeMehtod() { SomeDelegate del = delegate() { MessageBox.Show("Hello World!"); }; del (); } } |
匿名方法被定义为内嵌 (in-line) 方法,而不是作为任何类的成员方法。此外,无法将方法属性应用到一个匿名方法,并且匿名方法也不能定义一般类型或添加一般约束。
您应该注意关于匿名方法的两件值得关注的事情:委托保留关键字的重载使用和委托指派。
稍后,您将看到编译器如何实现一个匿名方法,而通过查看代码,您就会相当清楚地了解编译器必须推理所使用的委托的类型,实例化推理类型的新委托对象,将新的委托包装到匿名方法中,并将其指派给匿名方法定义中使用的委托(前面的示例中的 del)。
匿名方法可以用在任何需要使用委托类型的地方。您可以将匿名方法传递给任何方法,只要该方法接受适当的委托类型作为参数即可:
class SomeClass { delegate void SomeDelegate(); public void SomeMethod() { InvokeDelegate(delegate() { MessageBox.Show("Hello World!"); }); } void InvokeDelegate(SomeDelegate del) { del (); } } |
如果需要将一个匿名方法传递给一个接受抽象 Delegate 参数的方法,例如:
void InvokeDelegate(Delegate del);
则首先需要将匿名方法强制转换为特定的委托类型。
下面是一个将匿名方法作为参数传递的具体而实用的示例,它在没有显式定义 ThreadStart 委托或线程方法的情况下启动一个新的线程:
public class MyClass { public void LauchThread() { Thread workerThread = new Thread(delegate() { MessageBox.Show("Hello"); }); workerThread.Start(); } } |
在前面的示例中,匿名方法被当作线程方法来使用,这会导致消息框从新线程中显示出来。
匿名方法可以使用一般参数类型,就像其他方法一样。它可以使用在类范围内定义的一般类型,例如:
class SomeClass
{
delegate void SomeDelegate(T t);
public void InvokeMethod(T t)
{
SomeDelegate del = delegate(T item){...}
del
(t);
}
}
因为委托可以定义一般参数,所以匿名方法可以使用在委托层定义的一般类型。可以指定用于方法签名的类型,在这种情况下,方法签名必须与其所指派的委托的特定类型相匹配:
class SomeClass
{
delegate void SomeDelegate(T t);
public void InvokeMethod()
{
SomeDelegate
del
= delegate(int number)
{
MessageBox.Show(number.ToString());
};
del
(3);
}
}
关于委托的类型
C# 2.0允许我们在进行委托实例化时,省略掉委托类型,而直接采用方法名,C#编译器会做合理的推断。
//C#1.1的做法
addButton.Click += new EventHandler(AddClick);
Apply(a,new Function(Math.Sin);
//C#2.0的做法
addButton.Click += AddClick;
Apply(a,Math.Sin);
委托类型的参数列表和返回类型与匿名方法兼容问题
如果下列条件之一成立,则委托的参数列表与匿名方法兼容:
匿名方法没有参数列表并且委托没有 out 参数。
匿名方法包含的参数列表与委托的参数列表在数目、类型和修饰符方面都精确匹配。
如果下列条件之一成立,则委托的返回类型与匿名方法兼容:
委托的返回类型为 void,并且匿名方法没有 return 语句或只有无表达式的 return 语句。
委托的返回类型不为 void,并且与匿名方法中的所有 return 语句关联的表达式都可隐式转换为委托的返回类型。
委托的参数列表和返回类型都必须与匿名方法兼容才能进行从匿名方法到委托类型的隐式转换。
虽然乍一看匿名方法的使用可能像一种另类的编程技术,但是我发现它是相当有用的,因为在只要一个委托就足够的情况下,使用它就可以不必再创建一个简单方法。
下面一个有用的匿名方法的实际例子 — SafeLabel Windows 窗体控件。
Figure 10 The SafeLabel Control public class SafeLabel : Label { delegate void SetString(string text); delegate string GetString(); override public string Text { set { if(InvokeRequired) { SetString setTextDel = delegate(string text) {base.Text = text;}; Invoke(setTextDel,new object[]{value}); } else base.Text = value; } get { if(InvokeRequired) { GetString getTextDel = delegate(){return base.Text;}; return (string)Invoke(getTextDel,null); } else return base.Text; } } } |
Windows 窗体依赖于基本的 Win32 消息。因此,它继承了典型的 Windows 编程要求:只有创建窗口的线程可以处理它的消息。在 .NET 框架 2.0 中,调用错误的线程总会触发一个 Windows 窗体方面的异常。因此,当在另一个线程中调用窗体或控件时,必须将该调用封送到正确的所属线程中。Windows 窗体有内置的支持,可以用来摆脱这个困境,方法是用 Control 基类实现 ISynchronizeInvoke 接口,其定义如下:
public interface ISynchronizeInvoke
{
bool InvokeRequired {get;}
IAsyncResult BeginInvoke(Delegate method,object[] args);
object EndInvoke(IAsyncResult result);
object Invoke(Delegate method,object[] args);
}
Invoke 方法接受针对所属线程中的方法的委托,并且将调用从正在调用的线程封送到该线程。因为您可能并不总是知道自己是否真的在正确的线程中执行,所以通过使用 InvokeRequired 属性,您可以进行查询,从而弄清楚是否需要调用 Invoke 方法。问题是,使用 ISynchronizeInvoke 将会大大增加编程模型的复杂性,因此较好的方法常常是将带有 ISynchronizeInvoke 接口的交互封装在控件或窗体中,它们会自动地按需使用 ISynchronizeInvoke。
例如,为了替代公开 Text 属性的 Label 控件,您可以定义从 Label 派生的 SafeLabel 控件。SafeLabel 重写了其基类的 Text 属性。在其 get 和 set 中,它检查 Invoke 是否是必需的。如果是这样,则它需要使用一个委托来访问此属性。该实现仅仅调用了基类属性的实现,不过是在正确的线程上。因为 SafeLabel 只定义这些方法,所以它们可以通过委托进行调用,它们是匿名方法很好的候选者。SafeLabel 传递这样的委托,以便将匿名方法作为其 Text 属性的安全实现包装到 Invoke 方法中。
C# 编译器从匿名方法指派推理哪个委托类型将要实例化的能力是一个非常重要的功能。实际上,它还提供了另一个叫做委托推理的 C# 2.0 功能。委托推理允许直接给委托变量指派方法名,而不需要先使用委托对象包装它。例如下面的 C# 1.1 代码:
class SomeClass
{
delegate void SomeDelegate();
public void InvokeMethod()
{
SomeDelegate del = new SomeDelegate(SomeMethod);
del
();
}
void SomeMethod()
{...}
}
现在,您可以编写下面的代码来代替前面的代码片断:
class SomeClass
{
delegate void SomeDelegate();
public void InvokeMethod()
{
SomeDelegate
del
= SomeMethod;
del
();
}
void SomeMethod()
{...}
}
当将一个方法名指派给委托时,编译器首先推理该委托的类型。然后,编译器根据此名称检验是否存在一个方法,并且它的签名是否与推理的委托类型相匹配。最后,编译器创建一个推理委托类型的新对象,以便包装此方法,并将其指派给该委托。如果该类型是一个具体的委托类型(即除了抽象类型 Delegate 之外的其他类型),则编译器只能推理委托类型。委托推理的确是一个非常有用的功能,它可以使代码变得简练而优雅。
我相信,作为 C# 2.0 中的惯例,您会使用委托推理,而不是以前的委托实例化方法。例如,下面的代码说明了如何在不显式地创建一个 ThreadStart 委托的情况下启动一个新的线程:
public class MyClass
{
void ThreadMethod()
{...}
public void LauchThread()
{
Thread workerThread = new Thread(ThreadMethod);
workerThread.Start();
}
}
当启动一个异步调用并提供一个完整的回调方法时,可以使用一对委托推理,如图 11 所示。首先,指定异步调用的方法名来异步调用一个匹配的委托。然后调用 BeginInvoke,提供完整的回调方法名而不是 AsyncCallback 类型的委托。