摘要:本文將簡單介紹 C# 3.0 的新語法:擴充方法(extension methods)。
程式列表 1:StringExtension 類別
由於 StringExtension 只是單純提供字串處理的工具類別,它並不需要保存什麼狀態資料,因此我們它宣告為靜態類別。靜態類別無法建立 instance,而且只能包含靜態方法,所以我們的 Reverse 方法也是宣告為 static。
於是,我們在應用程式中便可以這麼寫:
如何?看起來是不是好像 String 類別原本就有提供 Reverse 方法?不僅如此,原本的 Reverse 是靜態方法,現在卻使用 instance method 的方式呼叫了。是的,這就是 extension methods 所要達成的效果。
如果你覺得這種寫法還不賴,接著就來看看 StringExtension 類別的 Reverse 方法要如何修改才能讓它(看起來)成為 String 類別的一份子。很簡單,只要將「程式列表 1」的第 4 行改成這樣就行了:
這裡的神奇關鍵字是:this。當你在靜態方法的參數列中加入關鍵字 this ,它就變成了擴充方法,而你就可以在程式中使用 instance methods 的方式呼叫此方法。當然,原本的靜態方法的呼叫方式也還是可以用。
可是,編譯器怎麼知道我要擴充的是 String 類別呢?答案是:編譯器會將關鍵字 this 後面接著的型別視為該方法所欲擴充的類別。
再舉一個例子。假設我們有一個處理日期時間的工具類別,其中有一個 ToRocDateString 方法,程式碼如下:
這引出了擴充方法的另一項規則:欲擴充的類別必須放在參數列的第一個位置。也就是說,關鍵字 this 只能用在第一個參數型別。如果你將 this 加在第二個或後面的參數,程式將無法通過編譯。
在使用 ToRocDateString 擴充方法時,只需要傳入一個參數,像這樣:
雖然它很方便(想像一下如果你要擴充的是 System.Object 類別!),但還是要注意避免濫用,如果用得太多,可能就會發生擴充方法名稱衝突的情形。此外,對於不是使用 Visual Studio 來寫程式的人來說,那些擴充方法呼叫可能會讓他們挺傷腦筋的吧!
Happy coding :)
典型場景
經常開發 .NET 應用程式的人,相信多少都會建立一套自己專屬的類別庫,畢竟作為通用目的 .NET Framework 不可能滿足所有應用程式的需求。比如說,自己開發的類別庫可能也會提供字串工具類別、日期時間、檔案 I/O、資料處理等工具類別。就拿字串處理來說好了,.NET 的 String 類別沒有提供字串反轉的方法,如果我們要在自己的字串工具類別中提供這個方法,可能會這麼寫:程式列表 1:StringExtension 類別
1: public static class StringExtension
2: {
3: // 字串反轉
4: public static string Reverse(string s)
5: {
6: if (String.IsNullOrEmpty(s))
7: return "";
8: char[] charArray = new char[s.Length];
9: int len = s.Length - 1;
10: for (int i = 0; i <= len; i++)
11: {
12: charArray[i] = s[len - i];
13: }
14: return new string(charArray);
15: }
16: }
於是,我們在應用程式中便可以這麼寫:
程式列表 2:呼叫 StringExtension 類別的靜態方法
OK! 到這裡為止都是我們非常熟悉的寫法。接著進入正題:擴充方法。1: class Program
2: {
3: static void Main(string[] args)
4: {
5: string s = "123456789";
6: Console.WriteLine(StringExtension.Reverse(s));
7: }
8: }
改用擴充方法
試想一下,如果「程式列表 2」的第 6 行在處理字串反轉時可以這麼寫: 6: Console.WriteLine(s.Reverse());
如果你覺得這種寫法還不賴,接著就來看看 StringExtension 類別的 Reverse 方法要如何修改才能讓它(看起來)成為 String 類別的一份子。很簡單,只要將「程式列表 1」的第 4 行改成這樣就行了:
4: public static string Reverse(this string s)
可是,編譯器怎麼知道我要擴充的是 String 類別呢?答案是:編譯器會將關鍵字 this 後面接著的型別視為該方法所欲擴充的類別。
再舉一個例子。假設我們有一個處理日期時間的工具類別,其中有一個 ToRocDateString 方法,程式碼如下:
程式列表 3:DateTimeExt 類別
ToRocDateString 方法的用途是將傳入的 DateTime 物件格式化成中華民國年份的日期字串,它需要兩個參數,一個是 DateTime 物件,另一個是日期分隔字元。現在,如果要讓它成為 DateTime 類別的擴充方法,就只要在第 4 行的第一個參數前面加上 this:1: public static class DateTimeExt
2: {
3: // 將 DateTime 物件格式化成中華民國年份的日期字串.
4: public static string ToRocDateString(DateTime date, char separator)
5: {
6: int year = (date.Year - 1911);
7: return year.ToString() + separator + date.Month + separator + date.Day;
8: }
9: }
4: public static string ToRocDateString(this DateTime date, char separator)
在使用 ToRocDateString 擴充方法時,只需要傳入一個參數,像這樣:
Console.WriteLine(DateTime.Now.ToRocDate('/'));
你不用擔心呼叫擴充方法時會忘記要傳入哪些參數,Visual Studuo 的 IntelliSense 功能會提示你。結語
擴充方法並沒有增加 .NET runtime 的負擔,一切都是由編譯器幫我們搞定--編譯器會到程式引用的各個 namespaces 中搜尋符合條件的擴充方法。如果你用 ILDASM 或 Reflector 工具去看編譯出來的 IL code,你就會看到,呼叫擴充方法的程式碼其實還是編譯成靜態方法呼叫。雖然它很方便(想像一下如果你要擴充的是 System.Object 類別!),但還是要注意避免濫用,如果用得太多,可能就會發生擴充方法名稱衝突的情形。此外,對於不是使用 Visual Studio 來寫程式的人來說,那些擴充方法呼叫可能會讓他們挺傷腦筋的吧!
Happy coding :)
相關文章
您好:
回覆刪除問個笨問題,不知這種擴充方法,跟所謂的partial class的區別為何呢?是說如果用partial class會讓實際的類別或物件變大(更佔記憶體)呢? 謝謝!
Hi Dan0605,
回覆刪除擴充方法可以在沒有類別原始碼的情況下,為類別增加 methods。
Partial class 則通常是你自己撰寫的程式碼,主要就是讓你把一個類別的程式碼分開放在不同的檔案裡。也許那個類別很複雜,為了易讀和維護,你選擇把某些 methods/properties 放到另一個檔案裡面。又或者,像 Visual Studio 這類工具,因為會產生類別的基本框架程式碼,為了避免程式設計師動到這些工具產生的 code,就把這些 code 獨立出去,放在不同的檔案裡。使用 partial class 並不會導致類別或物件更大,或更占記憶體。
Michael,
回覆刪除謝謝回覆,所以二者最大區別應該是有沒有類別原始碼囉?
原來看完文章只是在想好像二者好像可以達到相同功能,雖然還是有目的上的不同!不過你再說明,我就更清楚了,謝謝!
另不知會不會分享有關泛型的內容呢?看你消化過的文字,比較容易吸收呢.XD
我是還沒有想過要寫泛型的文章,也許最近有空時,嘗試寫點泛型的入門文章看看吧 :)
回覆刪除