c#编程
介绍
委托和事件是任何 Windows 或 Web 应用程序的基础,它们允许开发者“订阅”用户执行的特定操作。因此,开发者不需要期待所有的事件并过滤出想要的部分,而是选择想要接收通知并对这些操作作出反应。
委托 是一种指示 C# 在事件触发时调用哪个方法的方式。例如,当你在表单上点击按钮时,程序会调用一个特定的方法。委托就是这种指针。委托的优点是你可以通知多个方法某个事件已经发生,若你希望如此。
事件 是 .NET 框架提供的通知,表示某个操作已经发生。每个事件包含有关特定事件的信息,例如,鼠标点击事件会告诉你点击了哪个鼠标按钮以及点击的位置。
假设你编写了一个仅对按钮点击作出反应的程序,以下是事件发生的顺序:
- 用户按下鼠标按钮并点击一个按钮
- .NET 框架触发
MouseDown
事件 - 用户释放鼠标按钮
- .NET 框架触发
MouseUp
事件 - .NET 框架触发
MouseClick
事件 - .NET 框架触发按钮上的
Clicked
事件
由于按钮的点击事件已经被订阅,程序会忽略其余的事件,而你的委托会告知 .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 void Procedure()
声明。这条声明本身并不执行任何工作,而是声明了一个名为 Procedure
的委托类型,该委托不接受参数且没有返回值。接着,在 Main()
方法中,语句 Procedure someProcs = null;
实例化了一个委托。该委托最初并没有引用任何方法。接下来的语句 someProcs += new
Procedure(DelegateDemo.Method1)
和 someProcs += new
Procedure(Method2)
向委托实例中添加了两个静态方法。注意,类名也可以省略,因为语句发生在 DelegateDemo
类中。语句 someProcs += new
Procedure(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 表达式
Lambda 表达式是实现匿名委托的更清晰方式。其形式为:
(type1 arg1, type2 arg2, ...) => expression
这等价于:
delegate(type1 arg1, type2 arg2, ...)
{
return expression;
}
如果只有一个参数,可以省略括号。类型名称也可以省略,让编译器从上下文中推断类型。在以下示例中,str
是一个字符串,返回类型是 int
:
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()
{
// 处理事件
}
尽管事件被声明为公开,事件不能直接在类外触发,只能在包含它的类中触发。