.NET中委托写法的演变(上):委托与匿名方法
可能有一件事情需要說在前面,那就是:委托本身其實(shí)從來沒有改變過,改變的一直都是委托的“寫法”。因此更確切地說,改變的只是“編譯器”。而本文所有內(nèi)容都用C#來實(shí)現(xiàn),其實(shí)談得也都是C#編譯器本身——但是其實(shí)VB.NET也有變化啊。再由于.NET版本和C#版本的關(guān)系也是非常密切的,因此全文就使用.NET版本進(jìn)行指代了。
.NET 1.x中委托的寫法
委托,如果不追究細(xì)節(jié),從表面上來看我們可以將其通俗地理解為一個(gè)安全的“函數(shù)指針”。當(dāng)然,這個(gè)函數(shù)指針其實(shí)也是一個(gè)對(duì)象,有自己的成員,也會(huì)封裝了被調(diào)用方的上下文等等。至于委托的定義和使用方式,則是這樣的:
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(...)的方式來創(chuàng)建一個(gè)委托對(duì)象。不過,作為委托對(duì)象內(nèi)部的方法它既可以是實(shí)例方法,也可以是靜態(tài)方法。此外,方法只需要匹配委托類型的簽名和返回值即可,方法參數(shù)的名稱不會(huì)成為約束。
嗯,就是這么簡單。
.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已經(jīng)可以省略,開發(fā)人員可以直接將方法賦值給一個(gè)委托對(duì)象的引用。當(dāng)然,這個(gè)改進(jìn)不值一提,.NET 2.0中委托寫法的關(guān)鍵在于引入了“匿名方法”:
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); }匿名方法,簡單地說就是內(nèi)聯(lián)在方法內(nèi)部的委托對(duì)象,它的關(guān)鍵便在于形成了一個(gè)閉包(委托執(zhí)行時(shí)所需的上下文)。如上面的代碼中,BeginGetResponse的第一個(gè)參數(shù)(委托)可以直接使用TestRequest方法的參數(shù)url,以及方法內(nèi)的“局部”變量request。如果沒有匿名函數(shù)這個(gè)特性的話,代碼寫起來就麻煩了,例如在.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);} }此時(shí),我們往往會(huì)發(fā)現(xiàn),開發(fā)人員需要花費(fèi)大量的精力,為一小部分代碼維護(hù)一大段上下文。例如在這段代碼中,我們會(huì)將url和request對(duì)象塞入一個(gè)object數(shù)組中,在回調(diào)函數(shù)中再通過危險(xiǎn)的Cast操作恢復(fù)數(shù)據(jù)。如果您希望“強(qiáng)類型”,那么只能為每個(gè)回調(diào)創(chuàng)建一個(gè)新的上下文對(duì)象,維護(hù)起來可能更加麻煩——要知道,在并行編程,異步調(diào)用越來越重要的今天,如果沒有匿名方法自動(dòng)保留上下文的特性,開發(fā)人員會(huì)為這些“額外工作”疲于奔命的。
可能您會(huì)說,匿名方法的可讀性不佳,因?yàn)樾枰皟?nèi)聯(lián)”。一個(gè)方法中內(nèi)聯(lián)太多,維護(hù)成本就上去了,所以匿名方法并不推薦使用。我想說的是,您錯(cuò)了。如果為了可維護(hù)性,要將方法獨(dú)立拆開,也可以利用匿名方法的優(yōu)勢:
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表達(dá)式,代碼可以寫的更簡單易讀:
public static void TestRequest(string url) {WebRequest request = HttpWebRequest.Create(url);request.BeginGetResponse(ar => TestAsyncCallback(ar, request, url), null); }匿名方法的作用
千萬不要小看匿名方法的作用,有些時(shí)候您認(rèn)為它的作用僅限于上文描述,只是因?yàn)闆]有在某些問題上踏前一步。例如,對(duì)于那些只需要“按需創(chuàng)建”,且要“線程安全”的對(duì)象,您會(huì)怎么做呢?沒錯(cuò),可以使用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;} }嗯,做的很漂亮!那么……這樣的屬性再來一個(gè),再來三個(gè),再來五個(gè)呢?可能有些朋友就會(huì)開始大段地Copy & Paste,于是錯(cuò)誤便難免了。這里有一件真人真事,以前某位同學(xué)在一堆這樣的代碼中迷茫了,說為什么用了這種方法,還是初始化了多次對(duì)象了?檢查了半天沒有看出問題來。最后發(fā)現(xiàn),原因是訪問了錯(cuò)誤的initialized變量(例如,在某個(gè)應(yīng)該訪問artistInitialized的地方訪問了articleInitialized)。可惜,大段時(shí)間已經(jīng)被浪費(fèi)了——更糟的是,心情也隨之變差了。
其實(shí),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表達(dá)式!
private Lazy<BigInstance> m_lazyInstance =new Lazy<BigInstance>(() => new BigInstance()); public BigInstance Instance { get { return this.m_lazyInstance.Value; } }如果沒有匿名方法,許多容易使用的編程模型和方式都難以開展。例如,我們就不會(huì)有CacheHelper,也不會(huì)有AsyncTaskDispatcher(上,下),也很難利用“延遲”所帶來的便利,更難以出現(xiàn)微軟并行擴(kuò)展、CCR等優(yōu)秀框架。可以這么說,如果您不善于使用委托,您如果不知道如何合適地使用匿名方法,您在不自知的情況下可能就已經(jīng)編寫了大量額外的代碼了。
老趙平時(shí)的工作之一,便是為項(xiàng)目提供各種擴(kuò)展API,可以讓程序員們更愉快地進(jìn)行開發(fā)工作,得到更好的生產(chǎn)力,讓代碼變得更加美好。如今C#有了匿名方法、Lambda表達(dá)式、表達(dá)式樹、擴(kuò)展方法等優(yōu)秀的語言特性,真讓我有“如魚得水”的感覺。因此,我對(duì)于Java這樣不思進(jìn)取的語言可以說深惡痛絕(Java朋友們趕快學(xué)習(xí)Scala吧)。在看閱讀大量Java開源項(xiàng)目代碼時(shí),我常有這樣的感覺:“如果是C#的話,利用匿名方法,這個(gè)類不就可以不寫,那個(gè)類就可以省略……”。沒錯(cuò),為了保留回調(diào)函數(shù)的上下文而創(chuàng)建一些類,對(duì)于C#程序員來說,的確是一件有些不可思議的事情。
至于Lambda表達(dá)式以及其他話題,我們下次再說吧。
匿名方法的缺點(diǎn)
匿名方法的優(yōu)勢在于自動(dòng)形成閉包,而它的缺點(diǎn)也是讓程序員“不自覺”地創(chuàng)建了閉包,這會(huì)讓某些對(duì)象的生命周期加長。例如在一開始的TestRequest方法中,表面上看起來url是參數(shù),request是局部變量,有些朋友可能會(huì)認(rèn)為它們在方法退出后就已經(jīng)準(zhǔn)備回收了。不過因?yàn)樾纬闪碎]包,url和request已經(jīng)“升級(jí)”為一個(gè)對(duì)象的域變量,它的生命周期延長了,延長至回調(diào)函數(shù)執(zhí)行完畢。因此,一不注意可能就會(huì)產(chǎn)生一些莫名其妙的情況。
其實(shí),這些都是“延遲”所帶來的陷阱,作為一個(gè)優(yōu)秀的開發(fā)人員,除了知道某個(gè)東西的作用和優(yōu)勢,也要知道它的問題,不是嗎?
轉(zhuǎn)載于:https://www.cnblogs.com/xiangshu/articles/2059572.html
總結(jié)
以上是生活随笔為你收集整理的.NET中委托写法的演变(上):委托与匿名方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数学 希腊字母 Greek Alphab
- 下一篇: Python测试演讲稿收集