委托的概念和委托类型的声明
什么是委托
- 委托是C#中由用户自定义的一个类型
- 类表示的是数据和方法的集合,而委托实际上是一个能够持有对某个或某些方法的引用的类
- 与其他类不同,委托类能拥有一个签名,并且它只能持有与它的签名相匹配的方法的引用
如何使用委托
- 声明一个委托类型
- 使用该委托类型声明一个委托变量
- 创建委托类型的对象,把它赋值给委托变量,该委托对象包含某个方法的引用,这个方法和第一步定义的签名一致
- 为委托对象增加其他方法
- 像调用方法一样调用委托。此时,委托持有的每一个方法都会依次去执行
委托与类的相似之处
如何声明委托
- 声明委托的语法与定义方法的语法类似,但没有方法体,声明的前面需要加关键字delegate
- 声明委托相对于声明一个新类,所以可以在任何声明类的相同地方声明委托,可以在另一个类的内部声明,也可以在任何类的外部声明。
- 可以在委托的声明上应用任意常见的访问修饰符,如public,private等
创建委托对象
创建委托与声明委托代码
声明两个类,一个是普通类,一个是静态类
创建委托,以及委托对象,让委托对象引用普通类和静态类中的方法:
委托的调用和委托数组
创建对象的赋值
委托的调用
委托数组
委托上方法的添加和移除
组合委托
同时也可以覆盖引用:
为委托添加方法
从委托中移除方法
委托的添加与删除测试
带返回值的委托以及调用
带返回值没有带参数的委托
声明带有返回值方法的普通类
声明带有返回值的委托
带有返回值的委托引用拥有返回值的方法
返回值的结果返回的都是最后一个方法返回的值。
带返回值并且带参数的委托
创建带有返回值的方法:
创建带有返回值的委托,并且委托带有参数:
调用带有返回值并且有参数的委托:
最终的结果为9,因为在执行自增的方法,虽然返回的结果是11,但是因为是值类型,外部num的值并没有改变,依旧是10,下次调用自减的方法传递的参数还是10,所以最终结果为9。如果你需要结果为10的话,就需要设置引用参数,使用引用参数方法改变了值,外部num就能接受到新的结果值,这时候要使用ref关键字来实现:同时委托里面也要声明ref关键字的参数,如下:
调用多播委托的异常处理
多播委托
声明异常类,在方法中抛出异常:
声明测试异常的委托
调用测试异常的委托
获取委托所引用的方法数组:(获取调用列表)
优化后,可以不会因为某一个方法抛出异常而不执行下次调用方法列表的调用:
匿名方法(一)
声明带有返回值并带有参数的委托
委托中,匿名方法的使用
匿名方法(二)
params关键字的作用:在编译代码的时候,C#看到params关键字,会自动将后面的变量值存储在当前声明params的变量里面。如下:
调用
扩展到如何使用委托来关联params呢
在声明委托对象的等号右边是不需要进行书写params的,只要在声明委托的时候带上params即可。
匿名方法(三)
Lambda表达式
匿名方法简化
Lambda表达式可以对匿名方法进行简化:
如果匿名方法没有参数(也就是委托没有参数的时候),则必须使用空的花括号
C#官方提供的委托
Action(16种)
使用Action的委托,C#内部还是自己声明的delegate类型的委托,唯一区别就是这个是C#是官方提供的,上面的委托是我们自己开发者定义的委托。
优点:开发者可以使用内置的Action委托,不用单独的定义委托。
缺点:使用Action不能有返回值,并且参数个数不超过16个,同时声明的参数类型需一致。
Func委托(17种==》16种参数类型+1种返回值类型)
优点:与Action相比,Func支持有返回值,并且参数类型不限制,可以编写多种不同的数据类型
对泛型(在这里是对Student类型进行排序)
创建student类
进行冒泡排序(第一个参数是需要比较的类型数组,第二个参数就是一个委托,也就是Func委托,前两个T表示泛型参数,可以接收任意两个参数,委托的返回值是int类型),在这里委托相当于作为了方法的参数,当调用BubbleSort()方法的时候,委托类型可以接收一个委托,而委托funAge引用了方法,会自动调用引用的方法。
在这里是针对学生类型的年龄进行从大到小排序,非常的妙!
Predicate委托
返回的只能是bool值,主要作为判断(某些)来处理。
参数介绍:
案例:实现1-100,所有的偶数打印
定义判断偶数方法逻辑:
方法接收起始值,结束值,以及需要的约束条件(也就是偶数的条件)
案例:实现所有学生年龄大于25岁的学生信息
事件
当事件触发的时候,会执行发布者,发布者会找到多个订阅者,然后回调执行这些订阅者上的方法(也就是订阅者对方法的引用,拥有了调用方法的权力)
四种主体的介绍:
事件与委托的关系
在C#中,如果声明了一个事件,那么在代码运行的时候,事件内部一定会产生一个私有的委托。
事件提供了对其私有委托的结构化范文,也就是编程者一定不能越过事件而去访问事件中的内部委托,因为它是私有的。要想操作的内部的私有委托,我们只能通过事件这样的封装去操作,这样的访问叫做结构化访问。我们可以看见事件和委托的关系就相当于普通类中属性对字段的封装一样,属性对字段封装后,我们也不能直接对字段进行访问,但是我们可以通过属性对于这个子弹的值进行修改或访问。
事件代码的五大组件:
事件代码的编写
声明茶叶委托类
订阅者提供事件处理程序(也就是回调函数)
事件的声明
在发布者类声明事件:
事件声明的注意事项
事件成员
订阅事件
“发布者定义的事件+=回调方法”
触发事件
一般触发事件是写在发布者类当中,
EventHandler标准事件
EventHandler的参数类型是TeaEventArgs,后续在回调函数里面可以进行接收。
声明派生自EventArgs的类:
回调函数就可以接收了:(this执行的是本身,也就是发布者)
触发事件:
总结:只要我们需要传送数据,我们就需要创建一个数据类,里面包含了需要传递的数据,并且这个类是继承EventArgs的。然后再声明委托的时候,需要把数据类作为委托的参数,在尖括号里面进行编写。其次在整个的传递过程中,在订阅者里面,也就是那个回调方法里面可以进行接收数据类,里面可以获取到多个你定义的数据信息。这时候万事俱备,可以发布事件了,发布事件里面有两个类型,一个是发布者,另一个是传递的数据类。
事件的显示声明和隐式声明
在之前说过,事件是对委托的封装,我们只能在事件上进行注册和移除这两个操作。
如果我们在一个类的内部,声明一个事件成员,而声明的格式就如下:我们把这种声明方式叫做隐式声明。如果对一个事件进行隐式声明,那么C#在编译到声明这句话的时候就会按照事件是对委托的封装,每个事件内部都有一个私有的委托,编译器会在greetingEvent内部生成一个我们看不到的私有委托,而这个私有委托的名字和这个事件的名字是一模一样的。
所以之所以在一个类的内部我们可以实现对事件的赋值以及虽然是事件,但是依然能够使用委托的方式调用来调用事件。是因为我们在类的内部改动实际上是对私有化的内部改动,这种方式是可取的
。但是一旦跳出类的外部,外部的类是无法访问私有的委托的。也就是下面这种情况:
在类的外部进行访问私有委托,这时候编辑器会把它当做事件成员来看,所以对事件我们是不能进行赋值操作的,也不可以直接传参调用的。
总结的来说,对于一个委托对象来说,可以+=,也可以-=,也可以进行=初始化,也可以直接传参进行调用。但是对于事件来说,只允许两种操作,也就是+=,-=(注册、移除)
显示声明
一旦我们使用显示声明的方式,用add,remove语句,那么C#在编译的时候,就不会自动的默认生成同名的私有委托,它就会按照我们自己写好的add,remove语句(类似属性对字段的封装一样)。这时候对事件就不能赋值
显示声明和隐式声明在不同场景中都会用到,需要明白它们之间的区别,并且要理解它们分别对事件的操作和委托的操作所允许的方式是不一样的。对事件只允许+=,-=,而对委托可以进行=初始化,以及传参数调用它。