委托(Delegate)和事件(Event)
观察者模式
定义了对象一对多依赖,当一个对象改变时,所有依赖者都会收到通知并自我更新。
需知
- 方法组转换
支持从(多个)方法到兼容的委托类型的隐式转换。 - 逆变性
方法获取的参数类型可以是(委托的)参数的类型的基类。 - 协变性
方法返回类型可以是从(委托的)返回类型派生的派生类。 - 回调
将方法(或委托实例)作为(能隐式转换的委托类型的)参数传给其他方法来决定何时调用。 - 委托是类,所有委托类型派生自System.MulticastDelegate(多播委托),而多播委托派生自System.Delegate类。
- 方法组转换
委托的使用
[访问修饰符] delegate [返回类型] [委托类型] ([参数类型 参数])
如:internal delegate void Mydelegate(int Num);
Mydelegate dele = new Mydelegate(Func_1)或者Mydelegate dele =Func_1 ;
dele+=Func_2;
dele-=Func_2;通过System.Delegate.Combine/System.Delegate.Remove将委托实例从调用列表中加入或移除,上述运算符重载方式是简化后的方式。通过管理委托实例数组,以委托链的形式来调用多个绑定的方法,这些方法会根据绑定的顺序来依次调用。
尽管在使用时可以直接使用添加/删减方法名的方式来增减回调函数,但编译器生成CIL代码时依然会自动创建对应的委托实例。事件的使用
[访问修饰符] event [委托类型] [事件名]
可使用-+运算符来注册/移除委托实例,使用方法调用的方式执行事件。事件是基于委托的一种具体使用方式,可以说是一种特殊的委托实例,委托可以看作是一个类,主要作用是用来定义回调函数的原型,并提供注册,删除和调用等基本功能。
事件的使用一般是以下几步:- 定义委托类型,确定回调方法原型。
- 定义事件成员。
- 定义触发事件的方法(条件)。
一般将事件的定义和触发所在实例视为被观察者,观察者通过访问被观察者身上的事件,并注册相对应的回调来对事件触发做出相应具体的反应。
事件响应的好处在于松耦合性,不必了解被观察者的行为细节,只需在事件触发前适当时机注册或移除监听,便能在事件触发时做出响应。泛型委托(预置)
- 无返回值,参数0~4个
public delegate void Action<T1,T2,T3,T4>(T1 arg1,T2 arg2,T3 arg3,T4 arg4);
- 有返回值,参数0~4个
public delegate TResult Func<T1,T2,T3,T4,TResult>(T1 arg1,T2 arg2,T3 arg3,T4 arg4 )
; - 返回类型始终为bool,参数只能有1个
public delegate bool Predicate<T>(T obj)
;
特别适用于匹配,筛选的情况使用。 - 返回类型始终为int,参数只能同类型的2个
public delegate int Comparison<T>(T x, T y)
;
可以调用参数对象的CompareTo比较大小,从而应用于排序。
声明:[泛型委托] [委托名];
- 无返回值,参数0~4个
匿名函数
省略返回类型及函数名,必须使用delegate 关键字。
Action DoSomething =delegate (){//do DoSomething};
DoSomething();某些情况会显得简洁高效,类似函数式编程的写法,可以在函数内部直接使用,注意结尾的分号。
闭包
Func<int, int> Closure()
{
int Count = 1;
return delegate(int deta) { return Count += deta; };
}
Debug.Log(Closure()(2));既然是函数式编程,自然绕不开闭包。上面写法类似于JS等动态类型语言,可以在函数内将一个处理局部变量的委托实例作为返回值暴露给外部调用。
lambda表达式
拉姆达表达式也是借用了函数式编程的概念,取自λ演算,在JS中叫箭头函数。
匿名函数改用lambda写法通常会变得更加简洁。lambda表达式使用规则:
情景 | 例子 |
---|---|
无参数,则直接使用() | Action act = () => { }; |
1个或以上参数,可不显示指定参数类型 | Action<int,int>act=(a,b)=>{}; |
1个参数可省略() | Action<int>act=a=>{}; |
带ref/out时必须显示指定参数类型 | delegate void Mydelegate(ref int a); Mydelegate act=(ref int a)=>{}; |
函数主体只有一个语句时可省略大括号 | Action<int> act=a=>a=10; |
函数主体只有返回语句时可省略return | Func<int>func=()=>10; |
函数主题有2个以上语句时不可省略大括号和return | Func<int,int>func=a=>{a=10;return a;}; |
委托的实现和写法简化大大提高了使用C#开发的灵活性和简洁度,是因为其中许多创建和管理方法或委托实例的步骤编译器已经帮我们做了。但开发过程还是需要根据实际需要来选择是否应用这些“捷径”,因为使用简洁写法很多时候可读性和代码复用性并不是那么好。