FROM P239
1.委托概述
通过以下步骤来使用委托。
(1)声明一个委托类型。委托声明看上去和方法声明相似,只是没有实现块。
(2)使用该委托类型声明一个委托变量。
(3)创建委托类型的对象,把它赋值给委托变量。新的委托对象包括指向某个方法的引用,这个方法和第一步定义的签名和返回类型一致。
(4)你可以选择为委托对象增加其他方法。这些方法必须与第一步中定义的委托类型有相同的签名和返回类型。
(5)在代码中你可以像调用方法一样调用委托。在调用委托的时候,其包含的每一个方法都会被执行。
你可以把delegate看作一个包含有序方法列表的对象,这些方法具有相同的签名和返回类型。如下图所示
□方法的列表称为调用列表
□委托保存的方法可以来自任何类或结构,只要它们在下面两点匹配:
--委托的返回类型
--委托的签名(包括ref和out修饰符)
□调用列表中的方法可以是实例方法也可以是静态方法
□在调用委托时,会执行其调用列表中的所有方法
2.声明委托类型
1 delegate void MyDel (int x); 2 // 关键字 返回类型 委托类型名 签名
返回类型和签名指定了委托接收的方法的形式。
以上示例中的声明指定了MyDel类型的委托只会接受不返回值并且有单个int参数的方法。下图左边演示了委托类型,右边演示了委托对象。
委托类型声明在两个方面与方法声明不同。委托类型
□以delegate关键字开头
□没有方法主体
p.s.虽然委托类型声明看上去和方法的声明一样,但它不需要在类内部声明,因为它是类型声明。
3.创建委托对象
有两种创建委托对象的方式,第一种是使用带new运算符的对象创建表达式,如下面代码所示:
new运算符的操作数的组成如下:
□委托类型名
□一组圆括号,其中包含作为调用列表中第一个成员的方法的名字。方法可以是实例方法或静态方法。
1 delvar=new MyDel(myInstObj.MyM1); //创建委托并保存引用 实例方法 2 dVar=new MyDel(SClass.OtherM2); //创建委托并保存引用 静态方法 3 4 delvar=myInstObj.MyM1; //创建委托并保存引用 实例方法 5 dVar=SClass.OtherM2; //创建委托并保存引用 静态方法
还可以使用快捷语法,如上方代码后两行所示,它仅由方法说明符构成。这段代码和之前的代码是等价的,其能够工作是因为在方法名称和其相应的委托类型之间存在隐式转换。
除了为委托分配内存,创建委托对象还会把第一个方法放入委托的调用列表。
我们还可以使用初始化语法在同一条语句中创建变量和初始化对象。
4.给委托赋值
由于委托是引用类型,我们可以通过给它赋值来改变包含在委托变量中的引用。旧的委托对象会被垃圾回收器回收。
e.g.下面的代码设置并修改了delVar的值。
1 MyDel delVar; 2 delVar=myInstObj.MyM1; //创建委托对象并赋值 3 …… 4 delVar=SClass.OtherM2; //创建新的委托对象并赋值
5.组合委托
委托可以使用额外的运算符来“组合”。这个运算最后会创建一个新的委托,其调用列表连接了作为操作数的两个委托的调用列表副本。
e.g.如下代码创建了3个委托。第3个委托由前面2个委托组合而成。
1 MyDel delA=myInstObj.MyM1; 2 MyDel delB = SClass.OtherM2; 3 MyDel delC = delA + delB; //组合调用列表
尽管术语组合委托(combining delegate)让我们觉得好像操作数委托被修改了,其实它们并没有被修改。事实上,委托是恒定的。委托对象被创建后不能再被改变。
6.为委托添加方法
尽管上一节我们知道了委托其实是不变的,不过C#提供了看上去可以为委托添加方法的语法,即使用+=运算符。
如下代码为委托的调用列表“增加”了两个方法。方法加在调用列表的底部。
1 MyDel delVar = inst.MyM1;//创建并初始化 2 delVar+=SCl.m3; //增加方法 3 delVar+=X.Act; //增加方法
在使用+=运算符时,实际发生的是创建了一个新的委托,其调用了列表是左边的委托加上右边方法的组合。然后将这个新的委托赋值给delVar。
你可以为委托添加多个方法。每次添加都会在调用列表中创建一个新的元素。
7.从委托移除方法
我们还可以使用-=运算符从委托移除方法。如下图所示:
与为委托增加方法一样,其实是创建了一个新的委托。新的委托时旧委托的副本——只是没有了已经被移除方法的引用。
如下是移除委托时需要记住的一些事项:
□如果在调用列表中的方法有多个实例,-=运算符将从列表最后开始搜索,并且移除第一个与方法匹配的实例。
□试图删除委托中不存在的方法没有效果。
□试图调用空委托会抛出异常。我们可以通过把委托和null进行比较来判断委托的调用列表是否为空。如果调用列表为空,则委托是null。
8.调用委托
可以像调用方法一样简单地调用委托。用于调用委托的参数将会用于调用调用列表中的每一个方法。(除非有输出参数,后面会介绍)
如果一个方法在调用列表中出现多次,当委托被调用时,每次在列表中遇到这个方法时它都会被调用一次。
示例:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace review 8 { 9 delegate void PrintFunction(); 10 class Test 11 { 12 public void Print1() 13 { 14 Console.WriteLine("Print1 -- instance"); 15 } 16 public static void Print2() 17 { 18 Console.WriteLine("Print2 -- static"); 19 } 20 } 21 class Program 22 { 23 static void Main(string[] args) 24 { 25 Test t = new Test(); //创建一个测试类实例 26 PrintFunction pf; //创建一个空委托 27 pf = t.Print1; //实例化并初始化该委托 28 pf += Test.Print2; 29 pf += t.Print1; 30 pf += Test.Print2; 31 if (null != pf) //确认委托由方法 32 pf(); //调用委托 33 else 34 Console.WriteLine("Delegate is empty"); 35 Console.Read(); 36 } 37 } 38 } 39 /* 40 * 输出如下: 41 * Print1 -- instance 42 * Print2 -- static 43 * Print1 -- instance 44 * Print2 -- static 45 * */
9.调用带返回值的委托
若委托有返回值并且在调用列表中有一个以上的方法,会发生下面的情况:
□调用列表中最后一个方法返回的值就是委托调用返回的值
□调用列表中所有其他方法的返回值都会被忽略
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace review 8 { 9 delegate int MyDel(); 10 class MyClass 11 { 12 int IntValue = 5; 13 public int Add2() 14 { 15 IntValue += 2;return IntValue; 16 } 17 public int Add3() 18 { 19 IntValue += 3;return IntValue; 20 } 21 } 22 class Program 23 { 24 static void Main(string[] args) 25 { 26 MyClass mc = new MyClass(); 27 MyDel mDel = mc.Add2; 28 mDel += mc.Add3; 29 mDel += mc.Add2; 30 Console.WriteLine("Value: {0}", mDel()); 31 Console.Read(); 32 } 33 } 34 } 35 /* 36 * 输出如下: 37 * Value: 12 38 * */
10.调用带引用参数的委托
如果委托有引用参数,参数值会根据调用列表中的一个或多个方法的返回值而改变。
□在调用委托列表中的下一个方法时,参数的新值(不是初始值)会传给下一个方法。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace review 8 { 9 delegate void MyDel(ref int x); 10 class MyClass 11 { 12 public void Add2(ref int x) { x += 2; } 13 public void Add3(ref int x) { x += 3; } 14 } 15 class Program 16 { 17 static void Main(string[] args) 18 { 19 MyClass mc = new MyClass(); 20 int x = 5; 21 MyDel mDel = mc.Add2; 22 mDel += mc.Add3; 23 mDel += mc.Add2; 24 mDel(ref x); 25 Console.WriteLine("Value: {0}", x); 26 Console.Read(); 27 } 28 } 29 } 30 /* 31 * 输出如下: 32 * Value: 12 33 * */
11.匿名方法
□匿名方法(anonymous method)是在初始化委托时内联(inline)声明的方法
如下图所示,右边的版本即使用了匿名方法来替代。
二者均输出:
25
26
11.1 使用匿名方法
可以在如下地方使用匿名方法
□声明委托变量时作为初始化表达式
□组合委托时在赋值语句的右边
□为委托增加事件时在赋值语句的右边
11.2匿名方法的语法
包含如下组成部分
□delegate类型关键字
□参数列表,如果语句块没使用任何参数则可以省略
□语句块,它包含了匿名方法的代码
1.返回类型
匿名方法不会显示地声明返回值。然而,实现代码本身的行为必须通过返回一个在类型上与委托的返回类型相同的值来匹配委托的返回类型。若委托由void类型的返回值,匿名方法就不能返回值。
2.参数
除了数组参数,匿名方法的参数列表必须在如下3方面与委托匹配:
□参数数量
□参数类型及位置
□修饰符
我们可以通过圆括号为空或省略圆括号来简化匿名方法的参数列表,但必须满足:
□委托的参数列表不包含任何out参数
□匿名方法不使用任何参数
3.params参数
如果委托声明的参数列表包含了params参数,那么匿名方法的参数列表将忽略params关键字。
e.g.在如下代码中:
□委托类型声明指定最后一个参数为params类型的参数。
□然而,匿名方法参数列表忽略了params关键字。
12.Lambda表达式
(简化匿名方法的语法)
转化方法:
□删除delegate关键字
□在参数列表和匿名方法主体之间放Lambda运算符=>。 读作 goes to
1 Mydel del = delegate (int x) { return x + 1; };//匿名方法 2 Mydel del = (int x) => { return x + 1; }; //Lambda表达式
编译器可以通过推断,允许我们更进一步简化Lambda表达式:
□编译器还可以从委托的声明知道委托参数的类型,因此允许我们省略类型参数 如le2所示
--带有类型的参数列表称为显式类型
--省略类型的参数列表称为隐式类型
□如果只有一个隐式类型参数,可以省略周围的圆括号,如le3所示
□Lambda表达式允许表达式的主体是语句块或表达式。如果语句块包含了一个返回语句,我们可将语句替换为return关键字后的表达式,如le4所示。
关于Lambda表达式的参数列表的要点:
□Lambda表达式参数列表中的参数必须在参数数量、类型和位置上与委托相匹配。
□表达式的参数列表中的参数不一定需要包含类型(隐式类型),除非委托有ref或out参数——此时必须注明类型(显示类型)
□如果只有一个参数,并且是隐式类型的,周围的括号可以被省略,否则必须有括号。
□如果没有参数,必须使用一组空的圆括号。