【C#进阶】委托那些事儿(一)
一、简单的委托
1.1 委托的声明:
c#当中,委托(delegate)是一种方法封装,也即委托对象可以作为一种传递方法的变量来使用。
委托也算是一种类,与类是平级的存在。在类中写delegate对象当然是允许的,毕竟c#也允许类中类。但是一般不这样做,委托的声明最好与类声明平级。
声明:
public delegate void actionname(); 关键字 返回类型 委托变量名(函数参数)
方法执行,有两种方式,效果相同:
testhandler.invoke(实参);
testhandler(实参);
示例:
public delegate void testhandler(string str); class program { static void main(string[] args) { testhandler t = delegate (string str) { console.writeline($"there will show : {str}"); }; // testhandler t = str => console.writeline($"there will show : {str}"); lambda表达式,上行的语法糖。 t.invoke("hello world !"); } }
在说到具体如何实现委托之前,先简单说明一下action、func是什么。
c#规则中,action和func都是方法对象(委托类型),两者的区别仅在于有无返回类型。
public delegate void action(); public delegate tresult func<out tresult>();
形参是允许添加多个的:
action<int, string, double> action /*============== f12 ================*/ public delegate void action<in t1, in t2, in t3>(t1 arg1, t2 arg2, t3 arg3);
func<string, string, string> func
/*============== f12 ================*/
public delegate tresult func<in t1, in t2, out tresult>(t1 arg1, t2 arg2); //(其中,tresult参数总写在形参说明的最后)
// 注:尖括号表示里面是泛型,在声明中,需要在<>内写入具体的类型。
实例化这个对象,这样写:
action<t1, t2> action = delegate(t1 arg1, t2 arg2) { 具体实现 }; 类型 变量名称 关键字 (方法入参) {..}
例如可以这样声明:
action<int, string, double> action = delegate(int a, string b, double c) { };
func<string, string, string> func = delegate { return "test.."; };
注:入参可以省略不写,但是注意可能会有一些小陷阱,见下例[1]:
// 启动新线程,.net2.0有4个线程构造函数: public thread(parameterizedthreadstart start) public thread(threadstart start) public thread(parameterizedthreadstart start, int maxstacksize) public thread(threadstart start, int maxstacksize) // 涉及的两个委托类型是: public delegate void threadstart() public delegate void parameterizedthreadstart(object obj) // 创建一个新线程的尝试: new thread(delegate () { //..}); new thread(delegate (object o) { //..}); new thread(delegate { //..});
// 编译器将不知道第三行delegate省却的形参到底是哪个委托,因为它既可能是threadstart,又可以是parameterizedthreadstart。
1.2 订阅的容器 event
首先,在此需要认同一个观点:
属性不是字段——很多时候,属性是字段的包装器,保护字段不被滥用。而包装器永远不可能是包装的东西。
知道如何写封装的方法后,接下来便是——如何去打包这些方法。即:如何把这些方法添加(或者说是整合)到某个对象上。这种“添加”,一般需要一个“盒子”,它的关键字名称叫作事件(event)。
把方法装进事件当中,c#规定了这两个符号,
◆ += 表示“订阅”,-= 表示“退订”;
◆ 实例化后,该实例的事件成员只能出现在 += 或 -= 符号的左边。
◆ 只要订阅了,事件一旦执行,所有订阅的方法都会执行。
姑且可以把事件当成一个方法集合(+= 看做add, -= 看做remove)。
声明格式见下(关键字 delegate类型 事件变量名称 = delegate {..}):
public event eventhandler click = delegate {};
( * 上行省却的方法参数,编译器将参考”方法规则” )
以上声明方法是赋值式的,即会替换掉该事件当前订阅的所有内容,此处要注意。
示例:
class program { static void main(string[] args) { test a = new test(); a.testevent += delegate() { console.writeline("first action."); }; a.testevent += secondaction; a.testevent += () => { console.writeline("third action."); }; a.testaction();
console.writeline("================"); a.testevent -= secondaction; a.testaction(); } public static void secondaction() { console.writeline("second action."); } } class test { public event action testevent; public void testaction() { if (testevent != null) testevent(); } }
这个例子是不是非常眼熟,和控件的事件订阅是一样的。
比如checkbox的勾选事件:
_checkbox.checked += _checkbox_checked; // tips:输入+=后长按tab键,ide将自动生成方法;+=后按一次tab,自动生成lambda表达式方法。 … private void _checkbox_checked(object sender, routedeventargs e) { }
同理鼠标点击事件、双击等等,只不过像是监听鼠标操作(点下左键、松开左键等等)的这一项交由c#来处理了。
注意:如果多次初始化界面,同一变量再次订阅,将订阅多次哦。
==================================================================================
下节预告:
属性不是字段——很多时候,属性是字段的包装器,保护字段不被滥用。包装器永远不可能是包装的东西。
注释:
[1] 自《深入理解c#》(第3版)jon skeet 著 姚琪琳 译
[2] 自 刘猛铁的c#学习视频
下一篇: 这样也可以洗澡呢?!