聲明擴展方法
我們並不能將任何的方法都作為擴展方法——擴展方法必須要有以下的一些特性:
擴展方法本身可以是泛型的, 可以有返回值, 除了第一個參數外, 也可以包含ref / out參數, 可以作為partial類的一部分, 可以在迭代塊當中來實現, 可以使用nullable類型——任何語法, 只要遵循了上述的約束.
我們把方法的第一個參數類型叫做extended type(被擴展的類型). 這並不是一個官方術語, 不過是一個有用的簡短表達方式.
上面的列表不僅僅提供了所有的限制, 同時也表明了將靜態方法變為擴展方法你需要做的步驟——加上this關鍵字. 以下的代碼將我們之前的例子改造為擴展方法:
1: public static class StreamUtil
2: {
3: const int BufferSize = 8192;
4: public static void CopyTo(this Stream input,
5: Stream output)
6: {
7: byte[] buffer = new byte[BufferSize];
8: int read;
9: while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
10: {
11: output.Write(buffer, 0, read);
12: }
13: }
14: public static byte[] ReadFully(this Stream input)
15: {
16: using (MemoryStream tempStream = new MemoryStream())
17: {
18: CopyTo(input, tempStream);
19: return tempStream.ToArray();
20: }
21: }
22: }
相比之前的例子, 我緊緊增加了兩個this修飾符, 同時將方法名稱從Copy改為CopyTo. 雖然現在在ReadFully當中看起來有點奇怪, 不過之後我們會看到這個改動會讓我們的調用代碼看起來更加自然.
調用擴展方法
簡單來說, 改造實例代碼來使用StreamUtil和改造該工具類一樣簡單. 這次, 我們不是要增加什麼代碼, 相反, 我們要去掉一些代碼. 下面的代碼使用我們提過的新的”語法”來調用CopyTo. 我說”新”實際上它根本就不新——因為它和我們一直使用的調用實例方法的語法一模一樣.
1: WebRequest request = WebRequest.Create("http://manning.com");
2: using (WebResponse response = request.GetResponse())
3: using (Stream responseStream = response.GetResponseStream())
4: using (FileStream output = File.Create("response.dat"))
5: {
6: responseStream.CopyTo(output);
7: }
在上面的代碼中, 至少它看起來像是我們讓reponse流來完成copying的操作. 實際上幕後依然是StreamUtil在完成實際的工作, 但代碼讀起來更加自然了. 編譯器在背後實際上僅僅是將CopyTo的調用轉到了對StreamUtil.CopyTo的調用, 並傳遞responseStream作為方法的第一個參數.
你可能會注意並沒有什麼特別的東西指示我們到底是正在調用Stream的擴展方法還是常規的實例方法. 如果使用VS2008, 當你把鼠標移到一個方法調用上面的時候, 如果它是一個擴展方法, 那麼將會出現一個明顯的提示. 另外智能提示也會幫你指出哪些方法是擴展方法, 一個向下的箭頭圖標會出現在擴展方法前. 當然我們並不需要仔細到觀察每一個方法來了解它是不是擴展方法, 因為多數情況下, 我們並不關心我們正在調用的到底是一個擴展方法還是一個實例方法.
在我們調用代碼中, 還有一件可能會讓人感到奇怪的事情, 那就是——StreamUtil根本就沒有不提到! 編譯器是怎麼知道我們是要使用擴展方法的呢? 待續!