解讀.NET中*延遲*特性的幾個陷阱_.Net教程
推薦:C#教程:Assembly類訪問程序集信息C#中通過Assembly類可以訪問程序集信息. 1.允許訪問給定程序集的元元素,包含可以加載和執(zhí)行程序集的方法; 2.加載程序集:使用靜態(tài)方法Assembly.Load(程序集名稱)或Assembly.LoadFrom(程序集完整路徑名); 3.屬性: FullName:程序集顯示名稱; 3.方法: Ge
.NET發(fā)展至今,其實各處都有“延遲(Lazy)”的痕跡,一個小小的“Laziness”給我們帶來了不少靈活性1。“延遲”的關(guān)鍵就在于“只在需要的時候處理數(shù)據(jù)”,老趙曾經(jīng)在多篇文章中提到了類似的概念,如《高階函數(shù)、委托與匿名方法》及《您善于使用匿名函數(shù)嗎?》。不過“延遲”本身也會給您帶來一些陷阱,某些陷阱您很有可能也曾經(jīng)遇到過。這篇文章便是總結(jié)了延遲特性的集中常見陷阱,并給出應(yīng)對方案。
重復(fù)運算
問題
“延遲”的本意是“減少計算”,但是如果您使用不當,很可能反而會造成“重復(fù)計算”。例如,我們首先構(gòu)建一個方法,它接受一個參數(shù)n,返回一個Func<int, bool>對象:
|
以下為引用的內(nèi)容: static Func<int, bool> DivideBy(int n) |
返回的Func<int, bool>對象會根據(jù)傳入的參數(shù)x,返回一個表示x能否被n整除的布爾值。在這過程中,還會向控制臺輸出一句話,例如:“10 can be divisible by 3? No”。每當看到這句話,則表明“經(jīng)過了一次判斷”。那么您是否知道,下面的代碼會輸出什么結(jié)果呢?
|
以下為引用的內(nèi)容: List<int> values = new List<int>(); var divideByTwo = values.Where(DivideBy(2)); foreach (var i in divideByTwoAndThree) { } |
結(jié)果如下:
|
以下為引用的內(nèi)容: 0 can be divisible by 2? Yes |
您是否發(fā)現(xiàn),無論是在遍歷divideByTwoAndThree和divideByTwoAndFive序列時,都會從原有的values序列里重新判斷每個元素是否能夠被2整除?這就是.NET 3.5中“Where”的延遲特性,如果您在這里沒有意識到這點,就可能會產(chǎn)生重復(fù)計算,浪費了計算能力。
解決方案
解決這個問題的方法就是在合適的時候進行“強制計算”。例如:
|
以下為引用的內(nèi)容: var divideByTwo = values.Where(DivideBy(2)).ToList(); |
結(jié)果就變成了:
|
以下為引用的內(nèi)容: 0 can be divisible by 2? Yes |
此時,在獲得divideByTwo序列時,就會立即進行計算,這樣在遍歷后兩者時就不會重復(fù)計算1,3,5等元素了。
異常陷阱
問題
請問您是否知道下面的代碼有什么問題?
|
以下為引用的內(nèi)容: public static IEnumerable<string> ToString(IEnumerable<int> source) foreach (int item in source) |
如果您沒有看出來的話,不如運行一下這段代碼:
|
以下為引用的內(nèi)容: static void Main(string[] args) foreach (var s in values) { } |
請問,運行上面的代碼是否會拋出異常?從代碼的意圖上看,在ToString方法的一開始我們會檢查參數(shù)是否為null,然后拋出異常——這本應(yīng)被catch語句所捕獲。但是事實上,代碼直到foreach執(zhí)行時才真正拋出了異常。這種“延遲”執(zhí)行違反了我們的實現(xiàn)意圖。為什么會這樣呢?您可以使用.NET Reflector反編譯一下,查看一下yield語句的等價C#實現(xiàn)是什么樣的,一切就清楚了。
解決方案
對于這個問題,一般我們可以使用一對public和private方法配合來使用:
|
以下為引用的內(nèi)容: public static IEnumerable<string> ToString(IEnumerable<int> source) return ToStringInternal(source); private static IEnumerable<string> ToStringInternal(IEnumerable<int> source) |
不妨再去查看一下現(xiàn)在的C#代碼實現(xiàn)?
資源管理
問題
由于是延遲執(zhí)行,一些原本最簡單的代碼模式可能就破壞了。例如:
|
以下為引用的內(nèi)容: static Func<string> ReadAllText(string file) |
使用using來管理文件的打開關(guān)閉是最容易不過的事情了,不過現(xiàn)在如果您通過ReadAllText(@"C:\abc.txt")方法獲得的Func<string>對象,在執(zhí)行時就會拋出ObjectDisposedException。這是因為原本我們意圖中的順序:
打開文件
讀取內(nèi)容
關(guān)閉文件
因為有“延遲”特性,這個順序已經(jīng)變?yōu)椋?/p>
打開文件
關(guān)閉文件
讀取內(nèi)容
這怎么能不出錯?
解決方案
有朋友說,這個容易:
|
以下為引用的內(nèi)容: static Func<string> ReadAllText(string file) return () => text; |
的確沒有拋出異常了,但是這也喪失了“延遲”的特點了。我們必須讓它能夠在調(diào)用委托對象的時候,才去打開文件:
|
以下為引用的內(nèi)容: static Func<string> ReadAllText(string file) |
值得一提的是,using完全可以配合yield語句使用。也就是說,您可以編寫這樣的代碼:
|
以下為引用的內(nèi)容: static IEnumerable<string> AllLines(string file) |
由此也可見C#編譯器是多么的強大,它幫我們解決了非常重要的問題。
閉包共享
問題
其實這個問題也已經(jīng)被談過很多次了,在這里提一下主要是為了保持內(nèi)容的完整性。您認為,以下代碼結(jié)果如何?
|
以下為引用的內(nèi)容: List<Action> actions = new List<Action>(); foreach (var a in actions) a(); |
它打印出來的結(jié)果是10個10,具體原因在《警惕匿名方法造成的變量共享》一文中已經(jīng)有過描述,概括而來便是:各個action共享一個閉包,導(dǎo)致其中的“i”并不是獨立的。
解決方案
解決這個問題的方法,只需讓不同閉包訪問的值相互獨立即可。如:
|
以下為引用的內(nèi)容: List<Action> actions = new List<Action>(); foreach (var a in actions) a(); |
關(guān)于“延遲”特性,您還有什么看法呢?
分享:解讀Entity Framework的默認值BUG前幾天常使用.Net 3.5里的Entity Framework做個網(wǎng)站的時候,發(fā)現(xiàn)了一個問題:添加記錄時,對于DateTime型的數(shù)據(jù),無法使用數(shù)據(jù)庫的默認值。 雖然不是什么嚴重的問題,但牛脾氣上來了 ,就行解決這個問題。 具體的情況是這樣的,我的數(shù)據(jù)庫有個Users表,三個
- asp.net如何得到GRIDVIEW中某行某列值的方法
- .net SMTP發(fā)送Email實例(可帶附件)
- js實現(xiàn)廣告漂浮效果的小例子
- asp.net Repeater 數(shù)據(jù)綁定的具體實現(xiàn)
- Asp.Net 無刷新文件上傳并顯示進度條的實現(xiàn)方法及思路
- Asp.net獲取客戶端IP常見代碼存在的偽造IP問題探討
- VS2010 水晶報表的使用方法
- ASP.NET中操作SQL數(shù)據(jù)庫(連接字符串的配置及獲取)
- asp.net頁面?zhèn)髦禍y試實例代碼
- DataGridView - DataGridViewCheckBoxCell的使用介紹
- asp.net中javascript的引用(直接引入和間接引入)
- 三層+存儲過程實現(xiàn)分頁示例代碼
- 相關(guān)鏈接:
- 教程說明:
.Net教程-解讀.NET中*延遲*特性的幾個陷阱
。