【我與接口】C# 擴展方法 優(yōu)雅的鏈式coding
語言的設計,真的是挺有意思的。第一次看這個代碼[1]時,旁人隨口了一句“哇,好多實心句號”。
當時馬上一個想法是——怎么實現(xiàn)的?返回了對象,然后再調用方法?然后就放下了,后來發(fā)現(xiàn),這個是真值得說一說的。
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#語言規(guī)則里面,告訴我們可以這樣寫:
string.IsNullOrEmpty(str);
后來,有一天,我很自然地就寫成這樣了str.IsNullOrEmpty,當然,IDE都沒有彈出IsNullOrEmpty這個函數(shù)我就知道不對了。
嗯,如果非要寫成str.IsNullOrEmpty這樣,大概會很麻煩。
首先string類型就是seal,重寫一個string類那就……
可以寫靜態(tài)方法,但其實也不美觀,因為還是要這樣 IsNullOrEmpty_test(str), 那和string.IsNullOrEmpty(str); 是沒有區(qū)別的。
這個時候,故事終于來到——拓展方法(Extension Methods)。
下個定義吧,好說話。
拓展方法,就是把對象自己作為第一個傳入?yún)?shù)的靜態(tài)方法。(這個“對象自己”,需要在形參前加個 this 前綴)
沒有規(guī)則總是會亂套的,拓展方法有些語法[2]:
|
l 必須在一個非嵌套的、非泛型的靜態(tài)類中; l 至少有一個參數(shù)(就是對象它自己); l 第一個參數(shù)必須附加this關鍵字前綴; l 第一個參數(shù)不能有其他任何修飾符(比如out或ref); l 第一個參數(shù)的類型不能是指針類型。 |
那么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:為了決定是否使用一個拓展方法,編譯器必須能區(qū)分拓展方法與某靜態(tài)類中恰好具有合適簽名的其他方法。為此,它會檢查類和方法是否具有System.Runtime.CompilerServices.ExtensionAttribute這個特性(它是.NET3.5新增的)。 |
順帶一提,前面提及的InputSimulator類確實這樣實現(xiàn)的,思想上是一致的。
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()返回的IEnumerable類型,這是另一個故事。
這篇寫得短一些,主要覺得講的內容還是保持內容一致性的好,關于LINQ的學習,下一篇繼續(xù)吧。
注釋:
[1] 自 https://archive.codeplex.com/?p=inputsimulator
[2] 自《深入理解C#》(第3版)Jon Skeet 著 姚琪琳 譯

浙公網安備 33010602011771號