1、什么是委托
当需要把方法传递给其他方法时,就需要使用委托。
我们习惯于把数据作为参数传递给方法,而有时某个方法执行的操作并不是针对数据进行的,而是要对另外一个方法进行调用。更麻烦的是,在编译时我们是不知道第二个方法是什么的,这个信息只能在运行时得到,所以需要把第二个方法作为参数传递给第一个方法。例如:
1、启动线程和任务——基类System.Threading.Thread的实例调用方法Start(),就可以启动一个线程。如果告诉计算机启动一个新实例,就必须说明要在哪里启动该序列;必须为计算机提供开始启动的方法和细节,即Thread类的构造函数必须带有一个参数(该参数定义了线程调用的方法)。
2、事件——一般的思路是通知代码发生了什么事件。GUI编程主要处理事件。引发事件时,运行库需要知道应该执行哪个方法。这就需要把处理事件的方法作为参数传递给委托。
c++中,函数指针是一个指向内存地址的指针。但它不是类型安全的(无法判断这个指针实际指向什么,更无从知晓参数和返回类型了),可以把任何函数传递给需要函数指针的方法。这不仅会导致一些类型安全问题,而且没有意识到:在进行面向对象编程时,几乎没有方法时孤立存在的,而是在调用方法前通常需要和类的实例相关联。所以.NET framework在语法上不允许使用这种直接方法。如果要传递方法,就必须把方法的细节封装在一种新的对象类型中,即委托。委托只是一种特殊类型的对象——我们之前定义的所有对象都包含数据,而委托包含的只是一个或多个方法的地址。
委托就是寻址方法的.NET版本。
2、声明委托
一般的,C#中使用一个类,分为两个阶段:1、定义一个类(即告诉编译器这个类由什么字段和方法组成);2、实例化该类的一个对象(除非只使用静态方法)。
使用委托时,也需要这两个步骤:1、定义委托(即告诉编译器这种委托表示哪种类型的方法);2、创建该委托的一个或多个实例(编译器将在后台创建表示这个委托的一个类)
声明委托的示例1:
delegate void DelegateNameInvoker(int x);
这个示例中,声明了一个委托DelegateNameInvoker,它指定该委托的每个示例都可以包含一个方法的引用,该方法带有一个int参数,并返回void。定义委托时,必须给出它所表示的方法的签名和返回类型等全部细节(类型安全)。理解委托的一个好方式是把委托视为给方法的签名和返回类型指定名称。
示例2:
delegate double TwoLongsOption(long first , long second);
定义了一个委托TwoLongsOption,该委托表示的方法有两个long参数,返回类型是double。
3、使用委托
定义好委托后,就可以创建它的一个实例,从而用该实例存储特定方法的细节。
下面的代码说明了如何调用委托:这是一个在int值上调用ToString()方法的一个相当冗长的方式。
class Program { private delegate string GetAString(); static void Main(string[] args) { int x = 40; GetAString getAString = new GetAString(x.ToString); Console.WriteLine(getAString()); } }
这段代码,用new实例化了类型为GetAString的委托,并对它进行初始化(引用整形变量x的ToString()方法)。委托在语法上总是接受一个参数的构造函数,这个参数就是委托引用的方法,且这个方法必须匹配委托的签名。
实际上,给委托实例提供圆括号与调用委托类的Invoke()方法完全相同。
委托推断:为了减少输入量,在需要委托实例(即new的时候)的每个位置可以只传送地址的名称。,下面的例子就是一个例子:
1 GetAString getAString1 = x.ToString;
getAString1和getAString表示相同的含义。
注意:输入形式不能为x.ToString()。x.ToString()会返回一个字符串对象。只能把方法的地址赋予给委托。
委托是类型安全的,但有趣的是,它不关心在什么类型的对象上调用该方法,甚至不考虑方法时静态方法还是实例方法(只要方法的签名匹配委托的签名即可)。
下面的代码,扩展了上面的例子,委托getAString在另外一个对象上调用其他2个方法,一个是实例方法,一个静态方法:
1 struct Currency 2 { 3 public uint Dollars; 4 public ushort Cents; 5 public Currency(uint dollars,ushort cents) 6 { 7 Dollars = dollars; 8 Cents = cents; 9 } 10 //实例方法 11 public override string ToString() 12 { 13 return $"{Dollars}.{Cents}"; 14 } 15 //GetCurrencyUint是一个静态方法 16 public static string GetCurrencyUint() => "Dollar"; 17 } 18 private delegate string GetAString(); 19 static void Main(string[] args) 20 { 21 int x = 40; 22 GetAString getAString = new GetAString(x.ToString); 23 Console.WriteLine(getAString()); 24 25 var currency = new Currency(22, 11); 26 //调用实例方法 27 getAString = currency.ToString; 28 Console.WriteLine(getAString()); 29 //调用类的静态方法 30 getAString = Currency.GetCurrencyUint; 31 Console.WriteLine(getAString()); 32 33 }
4、简单的委托示例
下面的代码描述了委托的其中一个小用途——把方法组合到一个数组中,这样就可以在循环中调用不同的方法了:
1 class MathOperations 2 { 3 public static double MultiplyBy2(double value) => value * 2; 4 public static double Square(double value) => value * value; 5 } 6 delegate double DoubleOp(double x); 7 static void Main(string[] args) 8 { 9 DoubleOp[] doubleOps = 10 { 11 MathOperations.MultiplyBy2, 12 MathOperations.Square 13 }; 14 15 for(int i = 0; i < doubleOps.Length; i++) 16 { 17 Console.WriteLine($"调用方法{i}:"); 18 ProcessDisplay(doubleOps[i], 2.0); 19 ProcessDisplay(doubleOps[i], 2.2); 20 } 21 22 } 23 24 private static void ProcessDisplay(DoubleOp doubleOp, double v) 25 { 26 Console.WriteLine($"变量是:{v},结果是:{doubleOp(v)}"); 27 }
运行结果: