.NET中委托写法的演变(上):委托与匿名方法
可能有一件事情需要说在前面,那就是:委托本身其实从来没有改变过,改变的一直都是委托的“写法”。因此更确切地说,改变的只是“编译器”。而本文所有内容都用C#来实现,其实谈得也都是C#编译器本身——但是其实VB.NET也有变化啊。再由于.NET版本和C#版本的关系也是非常密切的,因此全文就使用.NET版本进行指代了。
.NET 1.x中委托的写法
委托,如果不追究细节,从表面上来看我们可以将其通俗地理解为一个安全的“函数指针”。当然,这个函数指针其实也是一个对象,有自己的成员,也会封装了被调用方的上下文等等。至于委托的定义和使用方式,则是这样的:
public delegate int SomeDelegate(string arg1, bool arg2);public static int SomeMethod(string arg1, bool arg2) { return 0; }public class SomeClass {public int SomeMethod(string a1, bool a2) { return 0; }public event SomeDelegate SomeEvent; }static void Main(string[] args) {SomeClass someClass = new SomeClass();SomeDelegate someDelegate = new SomeDelegate(someClass.SomeMethod);someClass.SomeEvent += new SomeDelegate(SomeMethod); }
可见,在.NET 1.x中需要使用new DelegateType(...)的方式来创建一个委托对象。不过,作为委托对象内部的方法它既可以是实例方法,也可以是静态方法。此外,方法只需要匹配委托类型的签名和返回值即可,方法参数的名称不会成为约束。
嗯,就是这么简单。
.NET 2.0中委托的写法
.NET中的委托引入了范型,且写法略有简化:
public delegate TResult MyFunc<T1, T2, TResult>(T1 a1, T2 a2);public static int SomeMethod(string a1, bool a2) { return 0; }static void Main(string[] args) {MyFunc<string, bool, int> myFunc = SomeMethod; }
在.NET 2.0中,new DelegateType已经可以省略,开发人员可以直接将方法赋值给一个委托对象的引用。当然,这个改进不值一提,.NET 2.0中委托写法的关键在于引入了“匿名方法”:
public static void TestRequest(string url) {WebRequest request = HttpWebRequest.Create(url);request.BeginGetResponse(delegate(IAsyncResult ar){using (WebResponse response = request.EndGetResponse(ar)){Console.WriteLine("{0}: {1}", url, response.ContentLength);}},null); }
匿名方法,简单地说就是内联在方法内部的委托对象,它的关键便在于形成了一个闭包(委托执行时所需的上下文)。如上面的代码中,BeginGetResponse的第一个参数(委托)可以直接使用TestRequest方法的参数url,以及方法内的“局部”变量request。如果没有匿名函数这个特性的话,代码写起来就麻烦了,例如在.NET 1.x中您可能就必须这么写:
展开
折叠public static void TestRequest(string url) {WebRequest request = HttpWebRequest.Create(url);object[] context = new object[] { url, request };request.BeginGetResponse(TestAsyncCallback, context); }public static void TestAsyncCallback(IAsyncResult ar) { object[] context = (object[])ar.AsyncState;string url = (string)context[0];WebRequest request = (WebRequest)context[1];using (WebResponse response = request.EndGetResponse(ar)){Console.WriteLine("{0}: {1}", url, response.ContentLength);} }
此时,我们往往会发现,开发人员需要花费大量的精力,为一小部分代码维护一大段上下文。例如在这段代码中,我们会将url和request对象塞入一个object数组中,在回调函数中再通过危险的Cast操作恢复数据。如果您希望“强类型”,那么只能为每个回调创建一个新的上下文对象,维护起来可能更加麻烦——要知道,在并行编程,异步调用越来越重要的今天,如果没有匿名方法自动保留上下文的特性,开发人员会为这些“额外工作”疲于奔命的。
可能您会说,匿名方法的可读性不佳,因为需要“内联”。一个方法中内联太多,维护成本就上去了,所以匿名方法并不推荐使用。我想说的是,您错了。如果为了可维护性,要将方法独立拆开,也可以利用匿名方法的优势:
public static void TestRequest(string url) {WebRequest request = HttpWebRequest.Create(url);request.BeginGetResponse(delegate(IAsyncResult ar){TestAsyncCallback(ar, request, url);}, null); }public static void TestAsyncCallback(IAsyncResult ar, WebRequest request, string url) {using (WebResponse response = request.EndGetResponse(ar)){Console.WriteLine("{0}: {1}", url, response.ContentLength);} }
如果借助.NET 3.5中的Lambda表达式,代码可以写的更简单易读:
public static void TestRequest(string url) {WebRequest request = HttpWebRequest.Create(url);request.BeginGetResponse(ar => TestAsyncCallback(ar, request, url), null); }
匿名方法的作用
千万不要小看匿名方法的作用,有些时候您认为它的作用仅限于上文描述,只是因为没有在某些问题上踏前一步。例如,对于那些只需要“按需创建”,且要“线程安全”的对象,您会怎么做呢?没错,可以使用Double Check:
private object m_mutex = new object(); private bool m_initialized = false; private BigInstance m_instance = null;public BigInstance Instance {get{if (!this.m_initialized){lock (this.m_mutex){if (!this.m_initialized){this.m_instance = new BigInstance();this.m_initialized = true;}}}return this.m_instance;} }
嗯,做的很漂亮!那么……这样的属性再来一个,再来三个,再来五个呢?可能有些朋友就会开始大段地Copy & Paste,于是错误便难免了。这里有一件真人真事,以前某位同学在一堆这样的代码中迷茫了,说为什么用了这种方法,还是初始化了多次对象了?检查了半天没有看出问题来。最后发现,原因是访问了错误的initialized变量(例如,在某个应该访问artistInitialized的地方访问了articleInitialized)。可惜,大段时间已经被浪费了——更糟的是,心情也随之变差了。
其实,Copy & Paste很明显没有遵守DRY原则啊。为什么不把它们封装在一处呢?例如:
展开
折叠public class Lazy<T> {public Lazy(Func<T> func){this.m_initialized = false;this.m_func = func;this.m_mutex = new object();}private Func<T> m_func;private bool m_initialized;private object m_mutex;private T m_value;public T Value{get{if (!this.m_initialized){lock (this.m_mutex){if (!this.m_initialized){this.m_value = this.m_func();this.m_func = null;this.m_initialized = true;}}}return this.m_value;}} }
于是,之前的代码就可以简化成这样了:
private Lazy<BigInstance> m_lazyInstance =new Lazy<BigInstance>(delegate { return new BigInstance(); });public BigInstance Instance { get { return this.m_lazyInstance.Value; } }
还是太丑,上Lambda表达式!
private Lazy<BigInstance> m_lazyInstance =new Lazy<BigInstance>(() => new BigInstance()); public BigInstance Instance { get { return this.m_lazyInstance.Value; } }
如果没有匿名方法,许多容易使用的编程模型和方式都难以开展。例如,我们就不会有CacheHelper,也不会有AsyncTaskDispatcher(上,下),也很难利用“延迟”所带来的便利,更难以出现微软并行扩展、CCR等优秀框架。可以这么说,如果您不善于使用委托,您如果不知道如何合适地使用匿名方法,您在不自知的情况下可能就已经编写了大量额外的代码了。
老赵平时的工作之一,便是为项目提供各种扩展API,可以让程序员们更愉快地进行开发工作,得到更好的生产力,让代码变得更加美好。如今C#有了匿名方法、Lambda表达式、表达式树、扩展方法等优秀的语言特性,真让我有“如鱼得水”的感觉。因此,我对于Java这样不思进取的语言可以说深恶痛绝(Java朋友们赶快学习Scala吧)。在看阅读大量Java开源项目代码时,我常有这样的感觉:“如果是C#的话,利用匿名方法,这个类不就可以不写,那个类就可以省略……”。没错,为了保留回调函数的上下文而创建一些类,对于C#程序员来说,的确是一件有些不可思议的事情。
至于Lambda表达式以及其他话题,我们下次再说吧。
匿名方法的缺点
匿名方法的优势在于自动形成闭包,而它的缺点也是让程序员“不自觉”地创建了闭包,这会让某些对象的生命周期加长。例如在一开始的TestRequest方法中,表面上看起来url是参数,request是局部变量,有些朋友可能会认为它们在方法退出后就已经准备回收了。不过因为形成了闭包,url和request已经“升级”为一个对象的域变量,它的生命周期延长了,延长至回调函数执行完毕。因此,一不注意可能就会产生一些莫名其妙的情况。
其实,这些都是“延迟”所带来的陷阱,作为一个优秀的开发人员,除了知道某个东西的作用和优势,也要知道它的问题,不是吗?
转载于:https://www.cnblogs.com/xiangshu/articles/2059572.html
.NET中委托写法的演变(上):委托与匿名方法相关推荐
- 一起谈.NET技术,从.NET中委托写法的演变谈开去(中):Lambda表达式及其优势...
在上一篇文章中我们简单探讨了.NET 1.x和.NET 2.0中委托表现形式的变化,以及.NET 2.0中匿名方法的优势.目的及注意事项.那么现在我们来谈一下.NET 3.5(C# 3.0)中,委托的 ...
- 从.NET中委托写法的演变谈开去(中):Lambda表达式及其优势
在上一篇文章中我们简单探讨了.NET 1.x和.NET 2.0中委托表现形式的变化,以及.NET 2.0中匿名方法的优势.目的及注意事项.那么现在我们来谈一下.NET 3.5(C# 3.0)中,委托的 ...
- 服务器在机柜中的安装位置,服务器上机柜的安装方法
服务器上机柜的安装方法 内容精选 换一换 安装依赖时,使用pip3.7.5 install xxx命令安装相关软件时提示无法连接网络,且提示"Could not find a version ...
- js原生事件委托写法,jquery事件委托写法
什么是事件委托: 事件委托--给父元素绑定事件,用来监听子元素的冒泡事件,并找到是哪个子元素的事件.(不理解冒泡的可以去百度下) 定义: 利用事件冒泡处理动态元素事件绑定的方法,专业术语叫事件委托. ...
- ubuntu下firefox中,直接在网页上安装程序的方法
我不知道别人是怎么做的,反正这个问题恶心死了. ubuntu下安装软件,一般都是gogole到一个页面,然后上面告诉你把下面的代码输入到ternimal. sudo apt-add-repositor ...
- 匹夫细说C#:委托的简化语法,聊聊匿名方法和闭包
0x00 前言 通过上一篇博客<匹夫细说C#:庖丁解牛聊委托,那些编译器藏的和U3D给的>的内容,我们实现了使用委托来构建我们自己的消息系统的过程.但是在日常的开发中,仍然有很多开发者因为 ...
- C# 委托,匿名方法,lambda表达式使用方法
在 2.0 之前的 C# 版本中,声明委托的唯一方法是使用命名方法. C# 2.0 引入了匿名方法,而在 C# 3.0 及更高版本中,Lambda 表达式取代了匿名方法,作为编写内联代码的首选方式. ...
- linux的ftp轮询上传文件,Android中实现异步轮询上传文件
前言 前段时间要求项目中需要实现一个刷卡考勤的功能,因为涉及到上传图片文件,为加快考勤的速度,封装了一个异步轮询上传文件的帮助类 效果 先上效果图 设计思路 数据库使用的框架是GreenDao,一个非 ...
- 使用.NET中的Action及Func泛型委托
原文 http://www.cnblogs.com/skm-blog/archive/2013/05/24/3096294.html 委托,在C#编程中占有极其重要的地位,委托可以将函数封装到委托对象 ...
最新文章
- 你为什么高考会考砸?-论出题侧重点其实是故意的
- python中的常量可以修改吗_深入理解Python变量与常量
- 手机360浏览器怎么清空历史记录 手机360浏览器历史记录清空方法分享
- GAN 生成对抗网络论文阅读路线图
- 做个犀利的码农:如何持续培养/更新自己的开发技能
- 用VB无窗口透明Usercontrol编写透明浮动按钮
- 如何计算环形复杂度_数据结构与算法复杂度
- WinEdt LaTeX参考文献的交叉引用
- c语言题目详解——实现四舍五入
- Code.V光学设计学习(一)——入门介绍
- 钉钉如何实现原笔迹手写签批
- 姿态估计(人体关键点检测)之CPN
- R语言常见的数据类型及转换
- C语言的一维数组名和对数组名取地址
- 挂号信经过几次签收最终送达收件人?
- FAIR开源Detectron
- exe程序嵌入Winform窗体
- 在安装了zonealarm的机器上实现共享上网
- 无线传感网络 --ZigBee2-2定时器
- 土石坝渗流分析的目的