[C#][C# IN DEPTH筆記][002] C#1.0 委派(Delegate)

  • 8034
  • 0
  • 2010-01-06

摘要:[C#][C# IN DEPTH筆記][002] 委派(Delegate)

註1:將使用C#ID簡稱來代表『C# IN DEPTH 中文版』書名。
註2:引用書中語句時,將會註明頁數。

「」※◎『』:【】

委派(Delegate)

C#ID(P.33):「如果有一天要跟寫C的程式設計師解釋委派,相信C語言的函式指標(Function Pointer)
會是最好的用詞。事實上,委派也類似函式指標的概念,它可以將方法透過參數的形式來傳遞,並且
可以讓其他的方法來呼叫並操作,其中一個方式就是透過封裝來操作。

用函式指標的概念來理解似乎會比較容易一些,
而後文又以介面的方式來思考:「或許你可以將委派型別(delgate type)視為介面的方法,
而委派的實體則可以視為實作介面的物件。

介面(interface)簡單的說就是定義一些抽象的方法,但是不提供這些方法的實體,這些方法的實體是定
義在所繼承的類別上;而委派也是類似,宣告一個委派型別就像是建立一個介面,只不過委派型別比較
簡單,指定義輸入及輸出的長相,而委派實體也是函式的本體,只接受符合委派宣告中所定義的這些輸
入及輸出長相的函式。
參考資料:使用委派取代介面的時機 (C# 程式設計手冊)

在C#語言規格書(P.30)有提到:「委派型別以特定的參數清單和傳回型別,表示方法的參考。委派可以
讓您將方法視為實體,指定給變數並當做參數傳遞。委派和其他語言中函式指標的概念很類似;
但是與函式指標的差異在於,
委派為物件導向而且具有型別安全的特性。

簡單來說,委派就是將函式進行封裝,也就是將函式的輸出及輸入長相當作是一個型別,並且可以利用
這個型別來宣告變數,而這個變數所指向的地方也就是委派的實體,也就是函式實際執行的地方!這個
時候,函式(的名稱)就可以被當作被當成像是string、int一般的變數,可以被當作參數來傳遞!

其實,這個委派將函式當作物件般的目的在JavaScript中就已經存在了,例如像jQuery中,就有大量的
函數,其中像是callback參數就是只接受函式(function)當作其參數值,而且也支援匿名函式,只不過在
網頁程式上,型別安全性就比較薄弱一些,而且也無法限制所傳入的函數型態(函式簽章、方法簽章)!
不過,用JavaScript的方式與概念來了解也不錯,
JavaScript真是神奇! :-)

 

C#語言規格書中的範例:

using System; 
delegate double Function(double x); 
class Multiplier 
{ 
    double factor; 
    public Multiplier(double factor) { 
        this.factor = factor; 
    } 
    public double Multiply(double x) { 
        return x * factor; 
    } 
} 

class Test 
{ 
    static double Square(double x) { 
        return x * x; 
    } 
    static double[] Apply(double[] a, Function f) { 
        double[] result = new double[a.Length]; 
        for (int i = 0; i < a.Length; i++) 
            result[i] = f(a[i]); return result; 
    } 

    static void Main() { 
        double[] a = {0.0, 0.5, 1.0}; 
        double[] squares = Apply(a, Square); 
        double[] sines = Apply(a, Math.Sin); 
        Multiplier m = new Multiplier(2.0); 
        double[] doubles = Apply(a, m.Multiply); 
    } 
}

其中,第2行是一個委派宣告,也可以想成是宣告一個自訂型別,而這個型別的名稱
就叫做「Function」,它是屬於一個委派型別,這個型別的特性是只能夠指派(=)
『輸出為double的型別且只有一個double型別的輸入值的函數』來當這個型別所
宣告的變數的值。就像int型別,其特性就是只能夠儲存數字,string型別就只能接受
字串來當其變數的值是一樣的。

所以第19行的Apply函式裡面有一個「Function f」的參數,就是只能接受回傳值為
double且輸入值為double的函式當作參數傳進來,而變數f也就是會指向所傳入的
函式的本體,進而呼叫使用!所以在第27行中,是將Square這個函式當作參數值傳
入到Apply函式中;而第28行則是將靜態類別Math裡面的靜態函式Sin傳入;而第30行
中,m是Multiplier類別的實體,而將這個類別實體m的函式Multiply當作參數傳入!
上述這些傳入函式的共通點都是『回傳值為double且只有一個double的輸入』。

===================================================================

C#ID(P.34):「NOTE 委派的迷思:委派這個字常常會被誤解,這是因為委派通常
會被用來描述成委派型別(Delegate Type)以及委派實體(Delegate Instance),事實上
這兩個是有一些差異的,委派型別就只是單純的一種資料型別,就好像string型別一
樣;而委派實體是透過委派型別所建立的,就像我們建立了一個字串物件一樣,.....

※委派的宣告只能在類別(class)、名稱空間(Namespace)或是全域(Global)的範圍中,
無法於函式中進行委派宣告!

因此,上面的程式碼中,委派的使用也可以改成下列這樣:

static void Main() { 
    double[] a = {0.0, 0.5, 1.0}; 
    Function sqr = new Function(Square);
    double[] squares = Apply(a, sqr);
    Function sin = new Function(Math.Sin);
    double[] sines = Apply(a, sin); 
    Multiplier m = new Multiplier(2.0); 
    Function mtp = new Function(m.Multiply);
    double[] doubles = Apply(a, mtp); 
} 

※C#ID(P.35):委派實體可以透過靜態方法(Static Method)與實體方法(Instance Method)
來建立

※委派變數與委派實體(函式本體)間是一個對映(Map)的關係,而上述範例中所使用的
委派都是具名方法的委派。

※C#ID(P.36):在進行記憶體的垃圾回收(Garbage Collection)時,委派實體會避免
被CLR進行垃圾回收,如果生命週期較長的物件參考到生命週期較短的物件時,反而
會延長物件的生命週期,並導致記憶體無法回收。

這應該是會發生在透過實體方法來建立委派實體的時候!

===================================================================

C#ID(P.36):委派實體須透過Invoke方法來啟動,並且要傳入和委派型別相同的參數,
最後會回傳對應的型別。
註:委派的Invoke方法只能用在同步(Synchronous)呼叫,
我們可以透過BeginInvoke以及EndInvoke方法來非同步(Asynchronous)啟動委派實體。

 

點部落格 的標籤: , ,

簡單就是美 :: { 簡單其實很不簡單 }