語言的設計,真的是挺有意思的。第一次看這個代碼[1]時,旁人隨口了一句“哇,好多實心句號”。 當時馬上一個想法是——怎麼實現的?返回了對象,然後再調用方法?然後就放下了,後來發現,這個是真值得說一說的。 1. 神奇的鏈接(chaining) 1.1 拓展方法 想了很久該怎麼引入話題,或者這樣說,像這 ...
語言的設計,真的是挺有意思的。第一次看這個代碼[1]時,旁人隨口了一句“哇,好多實心句號”。
當時馬上一個想法是——怎麼實現的?返回了對象,然後再調用方法?然後就放下了,後來發現,這個是真值得說一說的。
var sim = new InputSimulator(); sim.Keyboard .ModifiedKeyStroke(VirtualKeyCode.LWIN, VirtualKeyCode.VK_R) .Sleep(1000) .TextEntry("notepad") .Sleep(1000) .KeyPress(VirtualKeyCode.RETURN) .Sleep(1000) .TextEntry("These are your orders if you choose to accept them...") .TextEntry("This message will self destruct in 5 seconds.") .Sleep(5000) .ModifiedKeyStroke(VirtualKeyCode.MENU, VirtualKeyCode.SPACE) .KeyPress(VirtualKeyCode.DOWN) .KeyPress(VirtualKeyCode.RETURN);
1. 神奇的鏈接(chaining)
1.1 拓展方法
想了很久該怎麼引入話題,或者這樣說,像這種寫法,
if (str == null || str == “”)
相信剛剛開始編程的時候都這樣寫過,而C#語言規則裡面,告訴我們可以這樣寫:
string.IsNullOrEmpty(str);
後來,有一天,我很自然地就寫成這樣了str.IsNullOrEmpty,當然,IDE都沒有彈出IsNullOrEmpty這個函數我就知道不對了。
嗯,如果非要寫成str.IsNullOrEmpty這樣,大概會很麻煩。
首先string類型就是seal,重寫一個string類那就……
可以寫靜態方法,但其實也不美觀,因為還是要這樣 IsNullOrEmpty_test(str), 那和string.IsNullOrEmpty(str); 是沒有區別的。
這個時候,故事終於來到——拓展方法(Extension Methods)。
下個定義吧,好說話。
拓展方法,就是把對象自己作為第一個傳入參數的靜態方法。(這個“對象自己”,需要在形參前加個 this 首碼)
沒有規則總是會亂套的,拓展方法有些語法[2]:
l 必須在一個非嵌套的、非泛型的靜態類中; l 至少有一個參數(就是對象它自己); l 第一個參數必須附加this關鍵字首碼; l 第一個參數不能有其他任何修飾符(比如out或ref); l 第一個參數的類型不能是指針類型。 |
那麼string.IsNullOrEmpty(str);能怎麼改?
class Program { static void Main(string[] args) { string str = ""; Console.WriteLine(str.IsNullOrEmpty()); } } static class NullUtil { public static bool IsNullOrEmpty(this string text) { return string.IsNullOrEmpty(text); } }
這樣看來,可能會有一些疑問,編譯器怎麼決定要使用的拓展方法?怎麼個“非請勿來”?
首先,如果總是會檢測一下是不是實例方法;
如果不是,就會去查一個合適的拓展方法。它會檢查當前的、引用的所有拓展方法。
TIPS:為了決定是否使用一個拓展方法,編譯器必須能區分拓展方法與某靜態類中恰好具有合適簽名的其他方法。為此,它會檢查類和方法是否具有System.Runtime.CompilerServices.ExtensionAttribute這個特性(它是.NET3.5新增的)。 |
順帶一提,前面提及的InputSimulator類確實這樣實現的,思想上是一致的。
public class KeyboardSimulator : IKeyboardSimulator { ... public IKeyboardSimulator KeyPress(VirtualKeyCode keyCode) { var inputList = new InputBuilder().AddKeyPress(keyCode).ToArray(); SendSimulatedInput(inputList); return this; } ... }
1.2 Lambda篩選與鏈接
LINQ裡面.where().Select().OrderBy().等等的“點點點”,就是基於拓展方法的思路。
var list = new List<string> { "a", "b", "c", "d", "a", "b", "c", "d", "a", "a" }; list = list.Where(a => a.Equals("a")).Reverse().ToList(); list.ForEach(a => Console.WriteLine(a));
在Where()點擊F12,見:
publicstatic IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) { if (source == null) throw Error.ArgumentNull(nameof (source)); if (predicate == null) throw Error.ArgumentNull(nameof (predicate)); if (source is Enumerable.Iterator<TSource>) return ((Enumerable.Iterator<TSource>) source).Where(predicate); if (source is TSource[]) return (IEnumerable<TSource>) new Enumerable.WhereArrayIterator<TSource>((TSource[]) source, predicate); if (source is List<TSource>) return (IEnumerable<TSource>) new Enumerable.WhereListIterator<TSource>((List<TSource>) source, predicate); return (IEnumerable<TSource>) new Enumerable.WhereEnumerableIterator<TSource>(source, predicate); }
另外有一點值得提,Where()返回的是重新new的集合,占據記憶體空間的。
這篇寫得短一些,主要覺得講的內容還是保持內容一致性的好,關於LINQ的學習,下一篇繼續吧。
註釋:
[1] 自 https://archive.codeplex.com/?p=inputsimulator
[2] 自《深入理解C#》(第3版)Jon Skeet 著 姚琪琳 譯