2009/07/08

C# Delegate 委派 使用方法

之前講到委派 (Delegate),當時不清楚做法。查了一些資料後,大概了解它是一個代理者的角色,代替你執行你要執行的方法。例如說你可以手動打開電視開關來收看電視,也可以利用電視遙控器 (代理者) 來幫你執行打開電視的工作。

C# 提供 delegate 修飾子來宣告一個委派方法:

public delegate String getNameDelegate(String n);

委派方法的參數個數、順序、型態乃至於回傳型態都必須跟要被委派的方法一樣 (但變數名稱可以不相同)。下面是我們要委派這個 getNameDelegate 方法呼叫的一個方法:

public static String getName(String name){
return "g1: " + name;
}


所以我們把 getName 想成打開電視機,getNameDelegate 當作遙控器,這樣可以多少理解吧?

要呼叫委派方法,有許多方法,隨著 C# 版本的演進已經越來越方便,就從最複雜的開始說起吧。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1 {
class Program {
public delegate String getNameDelegate(String n);

static void Main(string[] args) {
getNameDelegate g0 = new getNameDelegate(getName);
getNameDelegate g1 = getName;
getNameDelegate g2 = delegate(String n) { return "g2: " + n; };
getNameDelegate g3 = n => "g3: " + n;

Console.WriteLine(g0("0"));
Console.WriteLine(g1("1"));
Console.WriteLine(g2("2"));
Console.WriteLine(g3("3"));
Console.ReadKey();
}

public static String getName(String name){
return "g1: " + name;
}
}
}


首先是 getNameDelegate g0 = new getNameDelegate(getName); 這段,這是最早的方法。你就將 getNameDelegate 給實體化,並在其建構元上指定要代為執行的方法 (如 getName) 就可以了。

因為這樣太麻煩,所以後來的 C# 支援這種寫法:getNameDelegate g1 = getName; 是不是更直覺呢?

C# 2.0 中,增加了匿名方法的建立,像我們的 getName 這麼簡單的一行,可以簡化為
getNameDelegate g2 = delegate(String n) { return "g2: " + n; };
這樣,利用 delegate(){ } 來定義匿名方法。

C# 3.0 中,更以 Lambda 運算式取代匿名方法,一行 getNameDelegate g3 = n => "g3: " + n; 輕鬆打發。 n => "g3: " + n; 就是所謂的 Lambda 運算式。例如 x => x * x 就是「傳入 x,執行 x * x 並回傳」。

最後準備好 g1 等委派物件後,就可以開始執行了。執行的方式有兩種,一種是 g1.Invoke(); 來引發,另一種是更為直覺的 g1(); 直接執行。


這麼看起來委派只是代為執行,並沒有什麼好用的感覺啊。像上面的例子,我大可直接呼叫 getName() 就好,為什麼還要繞一圈以 getNameDelegate 包裝起來再呼叫呢?脫褲子放屁嗎?

getNameDelegate g1 = getName;
g1("Scribe");

getName("Scribe");

They're the same!

雖然我現在還搞不懂委派有什麼好的,不過我發現委派他可以一次呼叫代為多個方法,這樣就比較有用多了。例如我按下遙控器的開關,就可以幫我把電視、冷氣、電燈等開關一次打開,不是很方便嗎?所以下面要教你怎麼一次委派多個方法。

using System;

namespace ConsoleApplication1 {
class Program {
public delegate void consoleDelegate(String txt);
public delegate void printNameDelegate(String n, consoleDelegate p);

static void Main(string[] args) {
var g0 = new printNameDelegate(printName);
g0 += delegate(String n, consoleDelegate p) { p("Call from anonymous func: " + n); };
g0 += (n, p) => p("Call from Lambda: " + n);

g0("scribe", print2Console);
Console.ReadKey();
}

public static void printName(String name, consoleDelegate p) {
p(name);
}

public static void print2Console(String txt){
Console.WriteLine(txt);
}
}
}

Result:
scribe
Call from anonymous func: scribe
Call from Lambda: scribe


var g0 = new printNameDelegate(printName);
g0 += delegate(String n, consoleDelegate p) { p("Call from anonymous func: " + n); };
g0 += (n, p) => p("Call from Lambda: " + n);

上面三行分別以不同的方式創造出委派物件,跟上面範例的不同點是以 += 串接,讓 g0 一次代理多個方法,最後再一次執行。所以 g0() 的結果有三行。這樣就可以呼叫一個委派物件,同時作數件事情了。

值得一提的是本範例用了兩個 delegate,其中一個用在 print2Console 上,讓其他方法能夠叫用。printName 第二個參數就是 delegate,其實很像是方法的指標之類的。(因為不可以直接指定第二個變數型別為 print2Console,所以使用委派方式)


我比較喜歡 delegate{} 建立匿名方法跟直接使用 () 來呼叫委派物件,你呢?

參考資料:C# 筆記:重訪委派-從 C# 1.0 到 2.0 到 3.0

沒有留言:

張貼留言