C#学习笔记 委托

委托(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);
	}
}

猜你喜欢

转载自blog.csdn.net/qq_45349225/article/details/114045717