C#语法——委托,架构的血液
本篇文章主要介绍委托的应用。
委托是大家最常见的语法了,但会用与精通之间的差别是巨大的。
一个程序员如果不能精通委托,那么,他永远无法成为高级程序员。
所以,让我们把委托刻到血液里吧。
这样,你才能称为[Developer]。
委托的定义
什么是委托?
委托实际上是一种类型,是一种引用类型。
微软用delegate关键字来声明委托,delegate与int,string,double等关键字一样。都是声明用的。
下面先看下声明代码,这里声明了两个委托。
public delegate void TestDelegate(string message); public delegate int TestDelegate(MyType m, long num);
delegate既然是关键字,和int,string一样,那么,为什么delegate后又跟了一个void或者int呢?
如果他们是同等地位的关键字,为什么可以一起使用呢?
很简单,我们把delegate后面的 【void TestDelegate(string message)】理解为一个变量,是不是就清晰明了了一些。
我们把delegate关键字理解为,是用来专门来定义这种复杂的变量的。而这种复杂的变量可以包含一个返回值和任意数目任意类型的传入参数。
有没有感觉,这个复杂的变量特别像一个函数的定义。
没错,官方定义,委托类型的声明与方法签名相似。所以,这个复杂变量,的确,书写的方式就是与函数一样。
那么,为什么这个声明方式如此怪异呢,是因为,我们用delegate定义的变量,只能用函数赋值。赋值方式如下所示:
public delegate void TestDelegate(string message); public delegate long TestDelegate2(int m, long num); public static void Excute() { TestDelegate2 td = Double; } static long Double(int m, long num) { return m * num; }
委托的基本应用
学会了赋值以后,我开始使用委托。
委托的使用方式如下:
string result = td(51, 8); Console.WriteLine(result);
这里我们会发现,委托的使用方式与函数调用一样。
没错,它们的确是一样的。因为委托是用函数来赋值的,所以调用方式一样也并不奇怪,不是吗。
换一种说法,就是委托封装了一个函数。
如果委托是封装的函数,并且它又是引用类型。那么委托第一种常规的应用就浮现出来了。
那就是——引用类型的函数。
如果函数是引用类型,那么这个函数只要没被内存回收,就可以被调用。如果是public函数或者是public static函数,那么它能跨越的东西就更多了。
比如可以跨类调用,跨程序集调用等等。而这种用法,就是委托的基本应用。
匿名委托的应用
匿名委托的官方介绍:在 2.0 之前的 C# 版本中,声明委托的唯一方式是使用命名方法。 C# 2.0 引入匿名方法,在 C# 3.0 及更高版本中,Lambda 表达式取代匿名方法作为编写内联代码的首选方式。
看不懂没关系,我们直接来学习使用。代码如下:
delegate string anonymousDelegate(int m, long num); public static void Excute() { anonymousDelegate ad = delegate (int m, long num) { return m.ToString() + num.ToString(); };//2.0时代的匿名委托 anonymousDelegate ad2 = (m, num) => { return m.ToString() + num.ToString(); };//3.0以后匿名委托 }
如代码所示,匿名委托是Lambda表达式,不懂的同学就当它是有固定写法即可,不用讲什么道理,只要记住并应用即可。
匿名委托虽然减少了一点代码,但还是要求我们自己去声明委托。所有,还能再简写一点吗?
答案当然是,可以的。
Action与Func
Action与Func是微软为我们预先定义好了的,两个委托变量。其中Action是不带返回值的委托,Func是带返回值的委托。
可以说,Action与Func完全包含了,我们日常使用所需的,全部的,委托变量。
也就是说,我们可以不用再去自己手动声明委托了。
下面来看最简单的Action与Func的定义:
Action a1 = () => { }; Func<int> f1 = () => { return 1; };//必须写 return 1;
Action与Func是泛型委托,各支持16个入参变量。下面代码为一个入参的定义,多参数以此类推。
Action<int> a1 = (i) => { }; Func<string,int> f1 = (str) => { return 1;//必须写 return 1; };
委托的线程应用
委托的线程应用是委托的第二种用法,分为线程使用委托,和委托的异步应用两种。
我们先看线程使用委托。如下代码所示,一个无入参匿名Action和一个无入参匿名Func。
Task taskAction = new Task(() => { });//无入参匿名Action taskAction.Start(); Task<int> taskFunc = new Task<int>(() => { return 1; });//无入参匿名Func taskFunc.Start(); int result= taskFunc.GetAwaiter().GetResult();//获取线程返回结果
我们能看到两种委托应用,代码都非常简洁。
下面我们再来看委托的异步应用。首先看最简单的异步调用。
Action action = new Action(() => { }); IAsyncResult result = action.BeginInvoke((iar) => { }, null); Func<int> func = new Func<int>(() => { return 1; }); IAsyncResult resultfunc = func.BeginInvoke((iar) => { var res = func.EndInvoke(iar); }, null);
这里我们使用委托的BeginInvoke方法来开启线程,进行异步调用。如上面代码所示,这里介绍了Action与Func的最基础的异步应用。
委托,架构的血液
委托是架构的血液,如果系统中没有委托,那代码将堆叠到一起,比大力胶粘的都紧密。
就好比一碗汤面倒掉了所有的汤,只要它静放一个阵子,就会变成一坨面球,让你无从下嘴。
所以,委托是架构的血液,是框架的流畅的基石。
那么委托到底是如何流动的呢?
我们先从刚介绍过的委托的线程应用说起。
第一核心应用——随手线程:
我们在做开发的时候,一定接触过父类。父类是干什么的呢?父类通常是用来编写公共属性和函数,方便子类调用的。
那我们的委托的第一个核心应用,就是父类的公共函数,线程随手启动。如何随手开启呢?
首先,我们创建父类代码如下:
class BaseDelegateSyntax { public void AsyncLoad(Action action) { } public void AsyncLoad(Action action, Action callback) { IAsyncResult result = action.BeginInvoke((iar) => { callback(); }, null); } public void AsyncLoad<T>(Action<T> action, T para, Action callback) { IAsyncResult result = action.BeginInvoke(para, (iar) => { callback(); }, null); } public void AsyncLoad<T, R>(Func<T, R> action, T para, Action<R> callback) { IAsyncResult result = action.BeginInvoke(para, (iar) => { var res = action.EndInvoke(iar); callback(res); }, null); } }
我们看到上面的代码,父类中添加了四个异步委托的调用函数,接下来,我们就可以在继承该类的子类中,随手开启线程了。
子类代码如下:
class ChildDelegateSyntax : BaseDelegateSyntax { public void Excute() { //开启异步方法 base.AsyncLoad(() => { }); //开启异步方法,并且在异步结束后,触发回调方法 base.AsyncLoad(() => { }, ()=> { //我是回调方法 }); //开启异步有入参的方法,传递参数,并且在异步结束后,触发回调方法 base.AsyncLoad<string>((s) => { },"Kiba518", () => { //我是回调方法 }); //开启异步有入参的方法,传递字符串参数Kiba518,之后返回int型结果518, //并且在异步结束后,触发回调方法,回调函数中可以获得结果518 base.AsyncLoad<string,int>((s) => { return 518; }, "Kiba518", (result) => { //我是回调方法 result是返回值518 }); } }
看了上面的父子类后,是否感觉委托让我们繁杂的线程世界变简洁了呢?
第二核心应用——穿越你的世界:
接下来,我们来看委托的第二种核心用法,穿越的应用。
这个应用,是最常见,也最普通的应用了。因为委托是引用类型,所以A类里定义的委托,可以在被内存回收之前,被其他类调用。
我们经常会在各种论坛看到有人发问,A页面如何调用B页面的属性、方法、父页面获取子页面的属性、方法,或者子页面获取父页面的属性、方法。
其实,只要定义好委托,并将委托正确的传递,就可以实现穿越的调用了。
下面我们看下穿越应用的代码。
public class FirstDelegateSyntax { public FirstDelegateSyntax() { Console.WriteLine(" First 开始 " ); SecondDelegateSyntax sds = new SecondDelegateSyntax(()=> { Console.WriteLine(" First传给Second委托被触发 "); }); sds.Excute(); Console.WriteLine(" First 结束 "); } } public class SecondDelegateSyntax { public Action Action { get; set; } public SecondDelegateSyntax(Action _action) { Console.WriteLine(" Second的构造函数 "); Action = _action; } public void Excute() { Console.WriteLine(" Second的Excute被触发 "); Action(); } }
我们可以看到,我们传递的委托,穿越了自身所属的类。在SecondDelegateSyntax类中被触发了。
运行结果如下:
第三核心应用——回调函数:
世界上本没有回调函数,叫的人多了,也就有了。
请记住,所有的回调函数,都是委托的穿越应用,所有的回调函数;都是委托的穿越应用;所有的回调函数,都是委托的穿越应用。
重要的话要讲三遍。
因为委托是引用类型,所以可以被[址传递]。函数是不可以被传递的。
当你传递函数的时候,其实是匿名传递了一个委托的地址。
结语
委托是我们最常用的语法,它将函数封装成引用类型的变量,供其他单位调用。
因为委托的特质是引用类型,所以决定了委托是可以进行址传递。也就是说,委托是穿梭于我们系统代码中的列车。
我们可以在列车上放很多很多东西,在需要的站点,叫停列车,并将托运的东西搬下来使用。
所以,理论上,只要我们利用好委托,就可以大量减少冗余的代码。
但委托这种列车,是每个程序员都可以定义的,如果一个项目中有十个开发者,每个人都在定义委托,那么,就有可能出现定义了十个相同的委托的情况,这样就出现了撞车的现象。
所以委托在使用的时候,尽量做到有序传递,即预先做好列车的行驶路线,让委托按照路径运行。尽量不要定义可以被任何单位调用的公共委托。
如果需要公共委托,可以采取反射的方式来调用。
后面我会继续写事件,消息,反射等语法,敬请期待。
C#语法——元组类型
C#语法——泛型的多种应用
C#语法——await与async的正确打开方式
注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!
若您觉得这篇文章还不错,请点击下右下角的【推荐】,非常感谢!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
2018年8月中级前端开发推荐书籍
这些书籍适合中级前端开发者阅读,初级前端开发人员买来除了自我安慰和垫高显示器以外可能没什么用处。 说是推荐书单,实际上是笔者自己的读书计划。欢迎你在阅读中与我交流。 1.《企业IT架构转型之道 阿里巴巴中台战略思想与架构实践》 推荐理由:曾经有一个前端大神说过,如果你想成为前端架构师,首先你得忘记自己是个前端。 2. 《Redis实战》 推荐理由: Redis是服务端的缓存技术。缓存可以说只在性能优化阶段才有明显的作用,项目初期往往用不到。大多数使用node.js进行后端开发的前端开发者实际上更多地是做中间层或者直接做后端服务,缓存技术对自己来说是技术盲区,私以为对许多开发者来说也是盲区。 3. 《大话数据结构》 推荐理由:技术决定你能走多快,数据结构和算法决定你能走多远。 4. 《算法图解》 推荐理由:技术决定你能走多快,数据结构和算法决定你能走多远。 5. 《CSS世界》 推荐理由:CSS和javascript一样,都是很容易上手,却很难精通的。CSS几乎可以说是我们全团队的软肋,包括前端人员在内,敢说自己精通js的大有人在,但敢说自己精通CSS的寥寥无几。这本是前端知名博主张鑫旭...
- 下一篇
Java基础复习(类的继承,接口的本质,抽象类,异常,包装类,泛型)
类的继承 为什么要针对接口编程?继承不好用吗?难道就因为java是单继承,所以才搞出接口来实现所谓的多继承?首先,继承是把双刃剑。 继承的好处: 代码复用,公共属性和方法可以丢到基类中去,子类只需要关注子类特有的就行。 通过基类可以方便统一处理不同的子类,如上转型对象。 继承的痛点 破坏了封装,封装可谓面相对象三大特性之一,是面相对象编程基本思维。那怎么破坏封装了呢?子类如果要重写(扩展)父类方法,要知道基类中方法实现的细节,要弄清楚父类中方法之间的依赖,比如子类要重写父类中的A方法,而父类中的A方法调用了这个父类中的B方法,那么就要再看一看B方法中的实现细节。否则会可能有错误,举例先忽略。同样的,父类中如果要修改方法,那也要考虑到子类。这样就破坏了封装性。如图: 2.破坏了is-a的关系,要知道继承关系就是is-a关系。那破坏从何说起?举个例子,比如说有个基类是鸟类,里面有个方法叫fly(),子类要重写这个方法来实现特定的功能。顾名思义,基类中是希望子类是有 飞 这个能力的,但如果子类有个是企鹅,它又不能飞,只会游和走,那该怎么办?就在企鹅的fly()中写游泳吗?这样虽然没问题,但显...
相关文章
文章评论
共有0条评论来说两句吧...