.NET 3.5擴(kuò)展方法和Lambda表達(dá)式
對(duì)于上文的簡(jiǎn)化需求,使用Lambda表達(dá)式和內(nèi)置的.NET 3.5擴(kuò)展方法便可以寫(xiě)成這樣:
- static List< int> EvenSquareLambda(IEnumerable< int> source)
- {
- return source.Where(i => i % 2 == 0).Select(i => i * i).ToList();
- }
.NET 3.5擴(kuò)展方法的延遲效果
應(yīng)該已經(jīng)有許多朋友了解了.NET 3.5中處理集合時(shí)擴(kuò)展方法具有“延遲”的效果,也就是說(shuō)Where和Select中的委托(兩個(gè)Lambda表達(dá)式)只有在調(diào)用ToList方法的時(shí)候才會(huì)執(zhí)行。這是優(yōu)點(diǎn)也是陷阱,在使用這些方法的時(shí)候我們還是需要了解這些方法的效果如何。不過(guò)這些方法其實(shí)都沒(méi)有任何任何“取巧”之處,換句話(huà)說(shuō),它們的行為和我們正常思維的結(jié)果是一致的。如果您想得明白,能夠自己寫(xiě)出類(lèi)似的方法,或者能夠“自圓其說(shuō)”,十有八九也不會(huì)有什么偏差。但是如果您想不明白它們是如何構(gòu)造的,還是通過(guò)實(shí)驗(yàn)來(lái)確定一下吧。實(shí)驗(yàn)的方式其實(shí)很簡(jiǎn)單,只要像我們之前驗(yàn)證“重復(fù)計(jì)算”陷阱那種方法就可以了,也就是觀察委托的執(zhí)行時(shí)機(jī)和順序進(jìn)行判斷。
好,回到我們現(xiàn)在的問(wèn)題。我們知道了“延遲”效果,我們知道了Where和Select會(huì)在ToList的時(shí)候才會(huì)進(jìn)行處理。不過(guò),它們的處理方式是什么樣的,是像我們的“普通方法”那樣“創(chuàng)建臨時(shí)容器(如List< T>),并填充返回”嗎?對(duì)于這點(diǎn)我們不多作分析,還是通過(guò)“觀察委托執(zhí)行的時(shí)機(jī)和順序”來(lái)尋找答案。使用這種方式的關(guān)鍵,便是在委托執(zhí)行時(shí)打印出一些信息。為此,我們需要這樣一個(gè)Wrap方法(您自己做試驗(yàn)時(shí)也可以使用這個(gè)方法):
- static Func< T, TResult> Wrap< T, TResult>(
- Func< T, TResult> func,
- string messgaeFormat)
- {
- return i =>
- {
- var result = func(i);
- Console.WriteLine(messgaeFormat, i, result);
- return result;
- };
- }
Wrap方法的目的是將一個(gè)Func< T, TResult>委托對(duì)象進(jìn)行封裝,并返回一個(gè)類(lèi)型相同的委托對(duì)象。每次執(zhí)行封裝后的委托時(shí),都會(huì)執(zhí)行我們提供的委托對(duì)象,并根據(jù)我們傳遞的messageFormat格式化輸出。例如:
- var wrapper = Wrap< int, int>(i => i + 1, "{0} + 1 = {1}");
- for (var i = 0; i < 3; i++) wrapper(i);
則會(huì)輸出:
- 0 + 1 = 1
- 1 + 1 = 2
- 2 + 1 = 3
那么,我們下面這段代碼會(huì)打印出什么內(nèi)容呢?
- List< int> source = new List< int>();
- for (var i = 0; i < 10; i++) source.Add(i);
- var finalSource = source
- .Where(Wrap< int, bool>(i => i % 3 == 0, "{0} can be divided by 3? {1}"))
- .Select(Wrap< int, int>(i => i * i, "The square of {0} equals {1}."))
- .Where(Wrap< int, bool>(i => i % 2 == 0, "The result {0} can be devided by 2? {1}"));
- Console.WriteLine("===== Start =====");
- foreach (var item in finalSource)
- {
- Console.WriteLine("===== Print {0} =====", item);
- }
我們準(zhǔn)備一個(gè)列表,其中包含0到9共十個(gè)元素,并將其進(jìn)行Where…Select…Where的處理,您可以猜出經(jīng)過(guò)foreach之后屏幕上的內(nèi)容嗎?
- ===== Start =====
- 0 can be divided by 3? True
- The square of 0 equals 0.
- The result 0 can be devided by 2? True
- ===== Print 0 =====
- 1 can be divided by 3? False
- 2 can be divided by 3? False
- 3 can be divided by 3? True
- The square of 3 equals 9.
- The result 9 can be devided by 2? False
- 4 can be divided by 3? False
- 5 can be divided by 3? False
- 6 can be divided by 3? True
- The square of 6 equals 36.
- The result 36 can be devided by 2? True
- ===== Print 36 =====
- 7 can be divided by 3? False
- 8 can be divided by 3? False
- 9 can be divided by 3? True
- The square of 9 equals 81.
- The result 81 can be devided by 2? False
列表中元素的執(zhí)行順序是這樣的:
***個(gè)元素“0”經(jīng)過(guò)Where…Select…Where,***被Print出來(lái)。
第二個(gè)元素“1”經(jīng)過(guò)Where,中止。
第三個(gè)元素“2”經(jīng)過(guò)Where,中止。
第四個(gè)元素“4”經(jīng)過(guò)Where…Select…Where,中止。
……
.NET 3.5擴(kuò)展方法的神奇之處
這說(shuō)明了,我們使用.NET框架自帶的Where或Select方法,最終的效果和上一節(jié)中的“合并循環(huán)”類(lèi)似。因?yàn)?,如果?chuàng)建了臨時(shí)容器保存元素的話(huà),就會(huì)在***個(gè)Where中把所有元素都交由***個(gè)委托(i => i % 3 == 0)執(zhí)行,然后再把過(guò)濾后的元素交給Select中的委托(i => i * i)執(zhí)行。請(qǐng)注意,在這里“合并循環(huán)”的效果對(duì)外部是隱藏的,我們的代碼似乎還是一步一步地處理集合。換句話(huà)說(shuō),我們使用“分解循環(huán)”的清晰方式,但獲得了“合并循環(huán)”的高效實(shí)現(xiàn)。這就是.NET框架這些擴(kuò)展方法的神奇之處1。
在我們進(jìn)行具體的性能測(cè)試之前,我們?cè)賮?lái)想一下,這里出現(xiàn)了那么多IEnumerable對(duì)象實(shí)現(xiàn)了哪個(gè)GoF 23中的模式呢?枚舉器?看到IEnumerable就說(shuō)枚舉器也太老生常談了。其實(shí)這里同樣用到了“裝飾器”模式。每次Where或Select之后其實(shí)都是使用了一個(gè)新的IEnumerable對(duì)象來(lái)封裝原有的對(duì)象,這樣我們遍歷新的枚舉器時(shí)便會(huì)獲得“裝飾”后的效果。因此,以后如果有人問(wèn)您“.NET框架中有哪些的裝飾器模式的體現(xiàn)”,除了人人都知道的Stream之外,您還可以回答說(shuō)“.NET 3.5中System.Linq.Enumerable類(lèi)里的一些擴(kuò)展方法”,多酷。
以上就介紹了.NET 3.5擴(kuò)展方法和Lambda表達(dá)式實(shí)現(xiàn)的高性能分解循環(huán)的委托方法。
【編輯推薦】