本篇文章說明 LINQ 最重要的組成元素:擴充方法,因為 LINQ 就是由一系列的擴充方法所組成的技術。
自學筆記這系列是我自己學習的一些心得分享,歡迎指教。這系列的分享,會以 C# + 我比較熟的 Net 3.5 環境為主。
另外本系列預計至少會切成【打地基】和【語法應用】兩大部分做分享。打地基的部分,講的是 LINQ 的組成元素,這部分幾乎和 LINQ 無關,反而是 C# 2.0、C# 3.0 的一堆語言特性,例如:型別推斷、擴充方法、泛型、委派等等,不過都會把分享的範圍限制在和 LINQ 應用有直接相關功能。
PS. LINQ 自學筆記幾乎所有範例,都可直接複製到 LINQPad 4 上執行。因為它輕巧好用,功能強大,寫範例很方便,請大家自行到以下網址下載最新的 LINQPad:http://www.LINQpad.net/。
LINQ 是一系列的擴充方法,讓開發人員可以藉由統一的資料存取模型,以相同的模式存取各種不同型態的資料來源。
由此可知擴充方法在 LINQ 的重要性。請注意,雖然 C# 和 VB 都支援擴充方法,但是撰寫方式差異有點大,例如:對 string 型別增加一個 RemoveAEIOU 的擴充方法,C# 是這樣寫:
public static class ExtensionString
{
public static string RemoveAEIOU(this string s)
{
string[] ary = s.Split(new char[] {'A', 'a', 'E', 'e',
'I', 'i', 'O' ,'o',
'U', 'u'},
StringSplitOptions.RemoveEmptyEntries);
return string.Join("", ary);
}
}
void Main()
{
string val = "This is beautiful world.";
Console.WriteLine(val.RemoveAEIOU());
}
//輸出:Ths s btfl wrld.
VB 的寫法如下(因為必須使用模組,LINQPad 我找不出方式,所以下面程式碼是 VS 的版本):
Imports System.Runtime.CompilerServices
Module Module1
Sub Main()
Dim val As String = "This is beautiful world."
Console.WriteLine(val.RemoveAEIOU())
End Sub
<Extension()> _
Public Function RemoveAEIOU(ByVal s As String) As String
Dim ary() As String = s.Split(New String() {"A", "a", "E", "e", _
"I", "i", "O", "o", _
"U", "u"}, _
StringSplitOptions.RemoveEmptyEntries)
Return String.Join("", ary)
End Function
End Module
看起來就大有不同對吧,我們看一下撰寫的規定:
C#:
VB:
很明顯,兩種語言撰寫擴充方法還真是大不同!
擴充方法講完了嗎?如果是如何撰寫,那已經講完了,可是有幾個問題,是我學習的時候曾有的疑問,也分享給大家。
首先就是我要怎麼辨識型別內建的方法和擴充方法?很簡單,透過 IntelliSense 的圖示和說明可以看到:
兩個地方看的出來,第一個是方法的圖示從飛過來的方塊變成方塊右邊有向下的箭頭;第二個是方法的說明,一開始就寫了 (擴充方法) 囉。
第二個問題:能不能擴充我們自己撰寫的類別?
答案是可以,只要此擴充方法的靜態類別認識這個類別即可!下述 LINQPad – C# Program 模式的範例,就表達了這件事:
void Main()
{
Person p = new Person { Name = "Leo, Taiwan" };
Console.WriteLine(p.GetName());
}
public class Person
{
public string Name { get; set; }
}
public static class ExtensionMethod
{
public static string GetName(this Person p)
{
return p.Name;
}
}
LINQPad 的 Program 模式,會把 void Main 獨立一個類別,Main 以外的就是同一個命名空間下其他類別,因為 Person 是 public 而且又在同一命名空間下,故擴充方法之靜態類別 ExtensionMethod 認得 Person。
再來個問題:如果擴充方法的名稱和傳入參數型別、數量都相同,可不可以?(也就是和現在型別的成員方法簽章完全一樣)
答案是,可以,編譯不會出錯,但是執行時期,系統會優先呼叫型別本身的成員方法,而不會直接取用擴充方法。換言之,如果擴充方法的簽章與型別中定義的方法相同,擴充方法一定不會被調用。
好了,大概要告一段落,但是我還想講講更深入一點,而且我覺得挺有趣的事,所以再給我一點版面囉!
物件的方法成員,其實有分兩種型態,一種叫靜態方法(static method),也就是有加 static 的方法,一種叫執行個體方法(instance method),兩種方法的叫用方式不一樣。
靜態方法,不須執行物件的建構式,必須透過類別名稱調用;執行個體方法,物件必須已被建立出執行個體才可呼叫。事實上,這兩種成員方法的呼叫方式是不能混用的,例如:string.Format()、string.Join() 就是靜態方法,而 "Leo".Split()、"Leo".ToUpper() 就是執行個體方法,我們不能用 "Leo".Format() 調用靜態方法,也不能用 string.ToUpper() 調用執行個體方法。
2013/7/8 修正下述內容,感謝 FrankWu 在點部落的回覆:
我曾經在某本書上看到這樣的說法: 「擴充方法可以像執行個體方法一樣通過一個執行個體被調用,也可以像靜態方法那樣通過類別名稱來調用」。
我之前誤解了這句話的意思,以為這裡所指的靜態方法調用,是以【擴充標的類別】來調用,其實這是大大的誤解。舉例來說,這次範例的 RemoveAEIOU 擴充方法,是擴充 string 類別,但是我先前以為,書中所指的靜態方法調用是 string.RemoveAEIOU(),結果就誤會大了:
其實正確的靜態方法調用之寫法應該是:
ExtensionString.RemoveAEIOU(val)
執行結果請見下圖:
擴充方法就講到這裡了,謝謝收看。