LINQ自學筆記-打地基-匿名委派

  • 3077
  • 0

LINQ自學筆記-打地基-匿名委派

Dotblogs 的標籤: , ,

因為參加 ITHome 的鐵人賽,所以整理了自己學習 LINQ 時的資料,變成自學筆記系列,歡迎指教。這系列的分享,會以 C# + 我比較熟的 Net 3.5 環境為主。

另外本系列預計至少會切成【打地基】和【語法應用】兩大部分做分享。打地基的部分,講的是 LINQ 的組成元素,這部分幾乎和 LINQ 無關,反而是 C# 2.0、C# 3.0 的一堆語言特性,例如:型別推斷、擴充方法、泛型、委派等等,不過都會把分享的範圍限制在和 LINQ 應用有直接相關功能。

PS. LINQ 自學筆記幾乎所有範例,都可直接複製到 LINQPad 4 上執行。因為它輕巧好用,功能強大,寫範例很方便,請大家自行到以下網址下載最新的 LINQPad:http://www.LINQpad.net/。

----------------本文開始----------------

因為匿名委派是具名委派的進化版本,為了方便大家比對、感覺差異,所以先看一下昨天「LINQ自學筆記-打地基-具名委派」除蟲的範例:

//宣告端 - 豪宅主人交代房子裡不能有蟲,不然就火掉我,所以我要先準備好蟲出沒的應變措施: 
public delegate string 除蟲(string 蟲); 
public class 豪宅 { 
    public void 蟲出沒(除蟲 人){ 
        Console.WriteLine(人("蟑螂")); 
    }
}
//邏輯端 - 執行業務的人 
public class 除蟲高手 { 
    public string 噴藥(string 蟲)  { 
        return "噴殺蟲劑讓 " + 蟲 + " 掛點。\n"; 
    } 
}
//呼叫端 - 這是管家 
void Main()  { 
  豪宅 白宮 = new 豪宅(); 
  除蟲高手 高手 = new 除蟲高手(); 
  除蟲 除蟲的人 = new 除蟲(高手.噴藥); 
  白宮.蟲出沒(除蟲的人); 
}

在呼叫端,.Net 2.0 先幫我們解決第一個麻煩的事情,就是不用自己再去建立委派的執行個體,由編譯器在編譯時期,幫我們建立委派類別的執行個體,所以呼叫端可以先精簡成:

void Main()  { 
  豪宅 白宮 = new 豪宅(); 
  除蟲高手 高手 = new 除蟲高手(); 
  //除蟲 除蟲的人 = new 除蟲(高手.噴藥); 
  除蟲 除蟲的人 = 高手.噴藥; 
  白宮.蟲出沒(除蟲的人); 
}

這是很合理的事情,因為委派類別很特殊,我們要建立它的執行個體唯二的目的就是:設定關聯的方法和呼叫會引動委派的方法並把委派丟進去,所以建立執行個體這件事讓編譯器來做並不難,也節省撰寫程式的數量。請注意,我們強調的是編譯器幫我們建立委派執行個體,並不是不用建立執行個體喔,請別誤會。

但是在上述呼叫端的程式碼中,我們還會發現,如果建立的委派只有設定一個關聯方法,接下來其實只會也只能做一件事,就是把委派當參數丟給會引動委派的方法,也就是把委派塞給某個方法去調用。那麼建立委派、設定關聯方法、呼叫執行方法,其實應該可以一條龍處理就好,而且其實在這種情況下,委派的名稱不重要,因此微軟也幫我們考量到了:

void Main()  { 
  豪宅 白宮 = new 豪宅(); 
  除蟲高手 高手 = new 除蟲高手(); 
  //除蟲 除蟲的人 = new 除蟲(高手.噴藥); 
  //除蟲 除蟲的人 = 高手.噴藥; 
  白宮.蟲出沒(高手.噴藥); 
}

這樣不只省去建立委派執行個體的程式,連建立委派的具名變數都省掉了,這就是「匿名委派」(Anonymous Delegate)。

PS. 匿名委派又稱之為匿名方法(Anonymous Method)。

再來講故事,在具名委派程式碼所描述的情境中,在真實情況裡有可能是這樣:

1. 管家手下那麼多人,其實並不需要找外面的人來處理蟲的問題。

2. 如果只是噴藥而已,管家並不用找特定人來處理,自己隨手拿藥來噴,或那個倒楣佣人正好經過,叫他去噴就好。

3. 除蟲其實也不見得只有噴藥這個手段,有可能是投藥餌或其他除蟲手段。

總結上述幾點,所以對於管家來說,若不用找外包,只要從自己手下找人除蟲,那找什麼人、用什麼手段其實不重要,反正就是要有蟲出沒,就叫人把蟲幹掉而已,因此程式碼可以精簡成:

//宣告端 - 豪宅主人交代房子裡不能有蟲,不然就火掉我,所以我要先準備好蟲出沒的應變措施: 
public delegate string 除蟲(string 蟲); 
public class 豪宅 { 
    public void 蟲出沒(除蟲 人){ 
        Console.WriteLine(人("蟑螂")); 
    }
}

//呼叫端 - 這是管家,手下那麼多人,不用外找,而且不一定要噴藥,也可以是其他手段 
void Main()  { 
  豪宅 白宮 = new 豪宅(); 
  白宮.蟲出沒(delegate(string 蟲) { 
                return "把 " + 蟲 + " 幹掉。\n";} 
              ); 
}

有沒有耳目一新的感覺!因為不用外包也不管是什麼人來除蟲,所以我們省去了建立邏輯端執行個體的程式碼;因為不管用什麼手段,只要達成目的即可,所以也省去撰寫方法名稱的部分,整個程式碼就精簡了。

最後這個範例一定要看懂,因為 LINQ 查詢中,大量使用了上述的方式,例如:

var query = numbers.Where(delegate(int n) {return n % 2 == 0;}); 
foreach (var n in query) 
{ 
    Console.WriteLine(n); 
}
//輸出:2 4

第一行是資料來源,第二行是 LINQ 語法,目的是從資料來源中取出 2 的倍數,最後利用 foreach 迴圈,逐一把結果輸出。當我們了解匿名型別,以後看到這種 LINQ 語法,應該都能看懂了。

PS. 上述語法可以透過型別推斷和 LINQPad 的 Dump 語法再精簡,不過為了方便閱讀程式,所以刻意保留較完整的寫法。

這一篇大幅精簡了委派中,呼叫端的程式碼,甚至把邏輯端也搬進呼叫端,在調用方法的同時,以匿名委派的方式同時把邏輯定義當參數傳遞進去,讓整個委派實用價值更高。下一篇,我們將來看看宣告端是否也可以用類似的方法精簡程式碼。

--------
沒什麼特別的~
不過是一些筆記而已