c#中的委托

主要介绍为什么要用委托,委托的一般形式,委托链,事件与观察者模型,委托的简写,Lambda表达式。

1. 为什么要用委托

Unity中的消息传递机制是通过SendMessage()和BroadcastMessage()函数的(我记得用C#写服务器端的代码,给客户端发送消息就用到过这两个函数,但是我用的应该是C#中socket相关的函数,这里不是,这里指的应该是之前开卷考试,我搜到的那两个函数)。

1. 这种机制是使用反射机制(反射机制本身就依赖与函数名或变量名或类名的字符串形式)来查找函数的,频繁使用反射会影响性能。

2. 函数的调用是通过函数名的字符串形式调用的,这样在编译的时候,即使你写错了名字也不会报错,只有在触发到要调用这个函数时,才会运行出错。这样就不能轻易发现错误。

3. 而且使用反射,类的私有成员或函数在类的内部没有被使用,但是在运行的时候却又可能使用它,但是维护人员在看到这个方法提示没有使用到时就会把它给删了。

综上,因为委托没有将函数名转换为字符串,所以可以在编译阶段判断有没有这个函数。

2. 委托的一般形式

首先,我们需要定义一个委托类型,它需要指明这种委托类型所委托的方法的参数与返回值:

delegate string myDelegate(int num);

myDelegate就是这个委托类型的名字(delegate 相当于class,myDelegate相当于类名),然后生成委托类型的实例(相当于生成类的对象):

myDelegate myd;

为该实例指定一个函数引用:

myd=function;

这其实是通过委托类型生成一个包装了函数引用的包装类  ,相当于:

myd=new myDelegate(function);

就像对象的初始化一样。

接下来,调用这个实例并传参就像调用一般的函数一样:

string str=myd(89);

但其实并不是将myd当做函数的名字调用,而是编译器知道没有函数的名字叫做myd,发现原来myd是一个委托实例,就会调用该实例的Invoke函数,所以上述写法相当于:

string str=myd.Invoke(89);

其实委托类型和从类生成一个对象,然后调用该对象的函数时一样的,其实所有的委托类型都是继承自MulticastDelegate类,MulticastDelegate类和一些系统的委托类型是继承自Delegate类,在Delegate类中实现了两个比较重要的方法,用来建立委托链。

既然所有的委托类(用户定义的)都继承自MulticastDelegate类,它们就有一些共同的属性,其中有三个字段:a,b,c(忘了系统取了什么名字了),a主要记录当前委托实例指向的方法是属于哪个类的,它会有这个类的对象的引用。b主要记录该委托指向的方法,c一般为null,所以用委托来运行一个方法,在内部也是通过该方法所在类的实例来调用的。

还有一种情况就是重载,当两个函数名都是function,但是参数不一样时,就依赖于委托类型的定义来判断到底是调用哪个函数了,所以这套回调方法机制又叫做方法组转换。

3. 委托链

如果委托一次只能调用一个方法,那还需要它干什么,为什么不直接调用函数呢?所以其实委托一般是委托链,可以同时调用好多个方法。基本形式如下:

delegate string myDelegate(int num);  

myDelegate  myds=null;

myDelegate myd1=function1;

myDelegate myd2=function2;

myDelegate myd3=function3;

myds=(myDelegate)Delegate.Combine(myds,myd1);  

myds=(myDelegate)Delegate.Combine(myds,myd2);

myds=(myDelegate)Delegate.Combine(myds,myd3);

string str = myds(230);

其中myds是一个委托链,里面具体是通过委托类中的第三个参数c来实现的,c引用一个委托数组,这样只要调用myds就相当于调用了三个函数。

既然可以想委托链中增加委托实例,当然也可以删除委托实例:

myds=(myDelegate)Delegate.Remove(myds,myd3);

在myds中的引用数组中,会从后向前检查类与方法是否与myd3中的一样,如果一样就删除。

4. 事件与观察者模型、事件与委托链的不同

首先,为了简化myds=Delegate.Combine(myds,myd1);这行代码,既然委托实例可以写作:myd=function;那么这行代码也可以写作:myds+=funtion1;这样运用+=或者-=来操作委托链。

观察者模型是一个设计模式。主要是事件触发公布者(主题Subject)持有一个委托链,当调用这个委托链时,所有包装器里指定的函数都会被执行,但是公布者无需自己在委托链中增减函数。增减函数这步操作被下放至消息接收者(观察者Observer)执行:消息接收者获得公布者的委托链,将自己需要被调用的函数,添加进委托链中,如果不再接收消息,则从中删除。当然参数值与返回值都要与委托链的委托类型一致。

这里的事件,感觉就是把一个委托链前面加了个event关键字,其他的操作完全一样啊,事件的+=与-=符号也可以使用,其实事件在编译时会生成两个函数,add_事件名、remove_事件名,在这连个函数中也是调用了Delegate的Combine函数与Remove函数实现的。但是加了这个关键词,就不能使用=操作,不能将一个函数直接赋给事件,而只能添加进事件或从事件中删除。其实这是对事件的一种保护,在观察者模型中,一个观察者只能控制自己的这部分,而无法将别人的订阅全部清空。

5. 委托的简写

因为委托写起来实在是太麻烦了,所以出现了委托的简写,

1. 不必构造委托对象:

将myd+=new myDelegate(function) 简写为:myd+=function,但其实对于编译器来说没有什么区别。

2. 匿名方法:

Action<int> myd=delegate(int a){

a=2;

}

myd(3);

这里的Action<int>是委托类型,myd是委托实例,=右边没有方法名。

使用匿名方法时,由于函数体写在了另一个函数体内,所以涉及一个概念:闭包

委托的闭包,当使用简写委托时,有时会连名字都不给,函数体就只能写在其他函数的里面,这时,它可以使用外函数的局部变量,这个函数体就称为闭包,闭包可以调用外函数的变量,这种所在作用域中有闭包的变量叫该闭包的外部变量,如果该闭包引用了该变量,该变量叫做捕获的外部变量。

当一个变量被引用时,而这个函数体的委托实例被外函数返回时,变量不会随着外函数的销毁而销毁,编译器会给他见一个临时类,保存这个变量,当返回的委托实例再次调用时,也是可以的。

Action<int> ac=func1();

ac(4);

Action<int> func1(){

int a=0;

Action<int> ac=delegate(b){

a=b+2;

}

ac(3);

return ac;

}

虽然return ac;之后,外函数被销毁了,但是delegate后的函数体不会随之销毁,变量a也被保存在托管堆中,所以并不是所有的函数局部变量都在栈上,比如捕获的外部变量。

6. 常用的四个泛型委托类型

Action<T>:没有返回类型,当然还有Action<>、Action<T1,T2>……,这里的T都是指函数参数的类型。

Func<T>:有返回类型,当然还有Func<T1,T2>……这里的最后一个T表示的是返回值的类型。

Predicate<T>:常常在过滤和匹配目标时用到。

Comparision<T>:关于排序的泛型委托类型:

这四个泛型委托类型就是不需要你去写:public delegate void Action<T>(T a),只需要直接使用就行,如Action<int> myd;但是函数体还是要自己写的。


 

7. Lambda表达式

对于委托中匿名函数的写法:

Func<int,string> myd=delegate(int i){

return ""+i;

}

Lambda表达式可以连delegate的关键字都省了,而是用=>符号来调用函数:

Func<int,string> myd =i=>{

return ""+i;

}

在=>的左边是形参,右边是函数体,当只有一个语句时,可以不写大括号。形参左边是委托实例。

但是其实,Lambda和匿名函数一样,本身并非委托类型,但是可以隐式或显式地转换成一个委托实例。在编译器中也会为Lambda表达式生成一个方法,方法名为了不和开发者一样,所以是以"<"开头的。Lambda表达式也存在闭包的情况,也匿名方法的闭包一样。

8. 另外,一般枚举型可以写在类外面。还有java中没有委托delegate的概念,java中虽然有这个类,但是其实和这里所说的委托没什么关系。而c#中的委托是用java中的反射实现的,可能是在Invoke方法中找到这个类的这个方法吧(这里存疑)。

但是在java中有观察者模型,java内置了观察者模型,在java.util包中包含有基本的Observer接口和Observable抽象类.许多功能比如说注册,删除,通知观察者的那些功能已经内置好了.猜测java中的观察者模型中实现的类似委托的功能是像C++那样通过List<抽象类>来实现的。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值