委托(delegate)与事件(event)是C#中提出的独特的概念,简单地说,委托是“函数指针”在C#中的更好实现,而事件是“回调函数”在C#中的更好实现。
委托类型与赋值
可以认为委托类似于函数指针,它是一种引用类型,它引用的就是函数。委托所引用的函数是有一定类型的,一个委托类型表示函数的签名(函数的参数类型及顺序),所以可以认为是类型安全的,即一种委托类型不能引用与之不兼容的任意类型;而一个委托实例可以表示一个具体的函数,及某个类的实例方法或静态方法,所以可以将委托理解为函数的包装或引用。
1.委托类型与委托变量的声明
声明格式如下:
修饰符 delegate 返回类型 委托名 (参数列表);
形式参数列表指定了委托的签名,而结果类型指定了委托的返回类型。
示例:
public delegate double MyDelegate(double x);
这就声明了一个委托类型,名叫MyDelegate
类型,它能引用的函数是这样的:带一个double
参数,而且返回的类型也是double
。
由于委托是一个类型,所以一般与class
的声明是并列的,不要写到class
的定义里面去。(如果写到class
里面,则成了嵌套类型。)
声明一个委托类型的变量与声明一个普通变量的方式一样:
委托类型名 委托变量名;
如:
MyDelegate d;
2.委托的实例化
对委托进行实例化,及创建一个委托的实例方法如下:
new 委托类型名(方法名);
其中方法名可以是某个类的静态方法名,也可以是某个对象实例的实例方法名。例如:
MyDelegate d1 = new MyDelegate(System.Math.Sqrt);
MyDelegate d2 = new MyDelegate(obj.MyMethod);
这里方法的签名及返回值类型必须与委托类型所声明的一致,也就是说,这个委托是类型严格的、类型安全的。
委托赋值的简写(语法糖): 在C#2.0 以上的版本中可以简写为:
MyDelegate d1 = System.Math.Sqrt;
MyDelegate d2 = obj.MyMethod;
3.委托的调用
委托的一个重要得特点是,委托在调用方法时不必关心该方法所属的对象的类型。它只要求所提供的方法的签名和委托的签名相匹配。
示例DelegateIntergral.cs
中计算数学函数积分:
delegate double Fun (double x);
class DelegateIntegral
{
static void Main (string[] args) {
Console.WriteLine ("Hello World!");
Fun fun = new Fun (Math.Sin);
double d = Integral (fun, 0, Math.PI / 2, 1e-4);
Console.WriteLine (d);
Fun fun1 = new Fun (Linear);
double d1 = Integral (fun1, 0, 2, 1e-3);
Console.WriteLine (d1);
Rnd rnd = new Rnd ();
double d2 = Integral (new Fun (rnd.Num), 0, 1, 1e-2);
Console.WriteLine (d2);
}
public static double Linear (double a) {
return a * 2 + 1;
}
public static double Integral (Fun f, double a, double b, double eps) {
// 积分计算
int n, k;
double fa, fb, h, t1, p, s, x, t = 0;
fa = f (a);
fb = f (b);
// 迭代初值
n = 1;
h = b - a;
t1 = h * (fa + fb) / 2.0;
p = double.MaxValue;
// 迭代计算
while (p >= eps) {
s = 0.0;
for (int i = 0; i < n; i++) {
x = a + (i + 0.5) * h;
s += f (x);
}
t = (t1 + h * s) / 2.0;
p = Math.Abs (t1 - t);
t1 = t;
n += n;
h /= 2.0;
}
return t;
}
}
class Rnd
{
Random r = new Random ();
public double Num (double x) {
return r.NextDouble ();
}
}
4.委托的合并(多播)
委托不仅仅是函数指针的包装,使用可合并的委托,其功能远胜过其他语言中的函数指针。
委托的可合并性,又称为多播(multicast),简单地说,可以一次调用多个函数。合并的委托实际上是对多个函数的包装,对这样的委托的调用,实际上是对所包装的各个函数的全部调用。其中多个函数又统称为该委托的调用列表。
事实上,编译器将委托都翻译成了System.MulticastDelegate
的子类,而委托的调用则翻译成了Invoke()
方法调用。
委托加减运算后的结果,如果其中不包含函数,则结果为null
。对等于null
的委托进行调用,运行时会发生一个NullReferenceException
异常,所以在调用一个委托之前,应该判断它是否为null
。
例DelegateMulticast.cs
使用多播委托:
delegate void D(int x);
class DelegateMulticast
{
static void Main(string[] args) {
Console.WriteLine("Hello World!");
DelegateMulticast.Test();
}
public static void M1(int i) {
Console.WriteLine("DelegateMulticast.M1: " + i);
}
public static void M2(int i) {
Console.WriteLine("DelegateMulticast.M2: " + i);
}
public void M3(int i) {
Console.WriteLine("DelegateMulticast.M3: " + i);
}
public static void Test() {
D cd1 = new D(DelegateMulticast.M1);
cd1(-1); // call M1
D cd2 = new D(DelegateMulticast.M2);
cd2(-2); // call M2
D cd3 = cd1 + cd2;
cd3(10); // call M1 then M2
cd3 += cd1;
cd3(20); // call M1, M2, then M1
DelegateMulticast d = new DelegateMulticast();
D cd4 = new D(d.M3);
cd3 += cd4;
cd3(30); // call M1, M2, M1, then M3
cd3 -= cd1; // remove last M1
cd3(40); // call M1, M2, then M3
cd3 -= cd4;
cd3(50); // call M1 then M2
cd3 -= cd2;
cd3(60); // call M1
cd3 -= cd2; // impossible removal is benign
cd3(60); // call M1
Console.WriteLine(cd3 == null); // False
cd3 -= cd1; // invocation list is empty
Console.WriteLine(cd3 == null); // True
//cd3 (70); // System.NullReferenceException thrown
cd3 -= cd1; // impossible removal
Console.WriteLine(cd3 == null); // True
}
}
5.委托的转换与相等
任何委托类型都是隐含地从System.MulticastDelegate
派生而来的。通过使用成员访问可以访问System.MulticastDelegate
类成员。但System.MulticastDelegate
自己不是一个委托类型,它是一个类类型。委托类型隐含为密封的(sealed),即不能从委托类型进行派生。
C#中的委托类型是名称等价的,并不是结构上的等价。也就是说,两个名称不同的委托类型,即使它们签名相同、返回类型也相同,它们仍被认为是不同的委托类型。如:
delegate void D(int a);
delegate void E(int a);
则D和E是两种不同的委托类型,它们不能互相转换。
对于两个委托实例,相等运算符(==
)有特殊含义。这是由于每种委托类型都隐含地提供了预定义的比较运算符。在下面情况下两个委托实例被认为相等:
- 两个都为
null
或者它们是同一委托实例的引用。 - 如果委托中都只含一个方法,它们指向的是同一静态方法或同一对象的同一实例方法。
- 如果委托中都只含多个方法,方法的个数相同、对应的方法相同、并且次序相等。
根据以上定义指,不同类型的委托也可能是相等的。
例DelegateEqual.cs判断两个委托是否相等:
delegate void D(int a);
delegate void E(int a);
class DelegateEqual
{
public static void M(int a) {
}
public static void Test() {
E e = new E(M);
D d = new D(M);
e += e;
d += d;
//e = (E)d; // 编译错误
//d += e;
Console.WriteLine(d.Equals(e));
//Console.WriteLine(d == e); // why??? 编译错误?
Console.WriteLine(d);
}
}