C# 编程/委托和事件
委托和事件是任何 Windows 或 Web 应用程序的基础,允许开发人员“订阅”用户执行的特定操作。因此,您不必预期所有情况并过滤掉您想要的内容,而是选择您想要接收通知的内容并对该操作做出反应。
委托是告诉 C# 在事件触发时调用哪个方法的一种方式。例如,如果您单击表单上的Button
,程序将调用特定方法。这个指针就是一个委托。委托很好,因为您可以根据需要通知多个方法发生了事件。
事件是 .NET 框架发出的关于已发生操作的通知。每个事件都包含有关特定事件的信息,例如,鼠标单击将说明在表单上的哪个位置单击了哪个鼠标按钮。
假设您编写了一个程序,该程序仅对Button
单击做出反应。以下是发生的事件顺序
- 用户在按钮上按下鼠标按钮
- .NET 框架引发
MouseDown
事件
- .NET 框架引发
- 用户释放鼠标按钮
- .NET 框架引发
MouseUp
事件 - .NET 框架引发
MouseClick
事件 - .NET 框架在
Button
上引发Clicked
事件
- .NET 框架引发
由于已订阅了按钮的单击事件,程序会忽略其余事件,而您的委托会告诉 .NET 框架在引发事件后调用哪个方法。
委托是 C# 中事件处理的基础。它们是抽象和创建引用方法的对象的结构,可用于调用这些方法。委托声明指定特定的方法签名。可以将对一个或多个方法的引用添加到委托实例。然后可以“调用”委托实例,这实际上会调用已添加到委托实例的所有方法。一个简单的例子
using System;
delegate void Procedure();
class DelegateDemo
{
public static void Method1()
{
Console.WriteLine("Method 1");
}
public static void Method2()
{
Console.WriteLine("Method 2");
}
public void Method3()
{
Console.WriteLine("Method 3");
}
static void Main()
{
Procedure someProcs = null;
someProcs += new Procedure(DelegateDemo.Method1);
someProcs += new Procedure(Method2); // Example with omitted class name
DelegateDemo demo = new DelegateDemo();
someProcs += new Procedure(demo.Method3);
someProcs();
}
}
在此示例中,委托由行delegate voidProcedure()
声明。此语句是一个完整的抽象。它不会生成执行任何工作的可执行代码,而只是声明一个名为Procedure
的委托类型,该类型不接受任何参数也不返回任何内容。接下来,在Main()
方法中,语句Procedure someProcs
= null; 实例化一个委托。赋值表示委托最初未引用任何方法。语句someProcs
+= newProcedure(DelegateDemo.Method1)
和someProcs
+= newProcedure(Method2)
将两个静态方法添加到委托实例。请注意,也可以省略类名,因为该语句位于DelegateDemo
内部。语句someProcs
+= newProcedure(demo.Method3)
将一个非静态方法添加到委托实例。对于非静态方法,方法名前面是对象引用。当调用委托实例时,将在添加方法到委托实例时提供的对象上调用Method3()
。最后,语句someProcs()
调用委托实例。现在将按添加顺序调用已添加到委托实例的所有方法。
可以使用-=
运算符删除已添加到委托实例的方法。
someProcs -= new Procedure(DelegateDemo.Method1);
在 C# 2.0 中,添加或删除委托实例中的方法引用可以缩短如下
someProcs += DelegateDemo.Method1;
someProcs -= DelegateDemo.Method1;
调用当前不包含任何方法引用的委托实例会导致NullReferenceException
。
请注意,如果委托声明指定返回值类型,并且将多个方法添加到委托实例,则调用委托实例将返回最后一个引用方法的返回值。其他方法的返回值无法检索(除非在返回值之外还显式存储在某处)。
匿名委托是编写委托代码的简短方法,使用delegate关键字指定。委托代码还可以引用声明它们的函数的局部变量。匿名委托由编译器自动转换为方法。例如
using System;
delegate void Procedure();
class DelegateDemo2
{
static Procedure someProcs = null;
private static void AddProc()
{
int variable = 100;
someProcs += new Procedure(delegate
{
Console.WriteLine(variable);
});
}
static void Main()
{
someProcs += new Procedure(delegate { Console.WriteLine("test"); });
AddProc();
someProcs();
Console.ReadKey();
}
}
它们可以像普通方法一样接受参数
using System;
delegate void Procedure(string text);
class DelegateDemo3
{
static Procedure someProcs = null;
private static void AddProc()
{
int variable = 100;
someProcs += new Procedure(delegate(string text)
{
Console.WriteLine(text + ", " + variable.ToString());
});
}
static void Main()
{
someProcs += new Procedure(delegate(string text) { Console.WriteLine(text); });
AddProc();
someProcs("testing");
Console.ReadKey();
}
}
输出为
testing testing, 100
Lambda 表达式是一种更清晰的方式来实现与匿名委托相同的效果。其形式为
(type1 arg1, type2 arg2, ...) => expression
这等效于
delegate(type1 arg1, type2 arg2, ...)
{
return expression;
}
如果只有一个参数,则可以省略圆括号。也可以省略类型名称,让编译器根据上下文推断类型。在下面的示例中,str
是一个 字符串,返回值类型是一个 整数
Func<string, int> myFunc = str => int.Parse(str);
这等效于
Func<string, int> myFunc = delegate(string str)
{
return int.Parse(str);
};
事件是一种特殊的委托,它可以促进事件驱动的编程。事件是类成员,无论其访问说明符如何,都无法在类外部调用。因此,例如,声明为公共的事件将允许其他类使用+=
和-=
操作事件,但触发事件(即调用委托)只允许在包含事件的类中进行。一个简单的示例
public delegate void ButtonClickedHandler();
class Button
{
public event ButtonClickedHandler ButtonClicked;
ButtonClicked += ()=>{Console.WriteLine("click simulation !");};
public void SimulateClick()
{
if (ButtonClicked != null)
{
ButtonClicked();
}
}
...
}
然后,另一个类中的方法可以通过将其中一个方法添加到事件委托来订阅事件
Button b = new Button();
b.ButtonClicked += ButtonClickHandler;
private void ButtonClickHandler()
{
//Handle the event
}
即使事件被声明为公共的,它也不能直接在包含它的类之外的任何地方触发。