iT邦幫忙

DAY 4
3

分享一些學習心得系列 第 4

LINQ自學筆記-打地基-擴充方法

本篇文章說明 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#:

  1. 擴充方法必須是撰寫在靜態類別中的靜態方法。
  2. 擴充方法的第一個參數必須用 this 關鍵字修飾,表示此方法所要依附的型別。
  3. 擴充方法不可以定義在巢狀類別或泛型類別中。

VB:

  1. 擴充方法必須撰寫在模組(Module)中。
  2. 擴充方法必須透過擴充屬性 <Extension()> 標記。(使用此擴充屬性必須匯入 System.Runtime.CompilerServices)
  3. 擴充方法的第一個參數就表示此方法所要依附的型別。

很明顯,兩種語言撰寫擴充方法還真是大不同!

擴充方法講完了嗎?如果是如何撰寫,那已經講完了,可是有幾個問題,是我學習的時候曾有的疑問,也分享給大家。

首先就是我要怎麼辨識型別內建的方法和擴充方法?很簡單,透過 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)

執行結果請見下圖:

擴充方法就講到這裡了,謝謝收看。


上一篇
LINQ自學筆記-打地基-區域型別推斷02
下一篇
LINQ自學筆記-打地基-泛型方法
系列文
分享一些學習心得30

尚未有邦友留言

立即登入留言