在CLI中,代理是對函數進行包裝的對象;而事件是一種為客戶程序提供通知的類機制。
在前幾篇文章中,已經多次演示了如果讓一個句柄在不同的時間,被引用至不同的對象,從而以更抽象的方法來解決程序中的問題,但是,也能使用代理通過函數來達到同樣的效果;代理是包裝了函數的一個對象,且對實例函數而言,也能通過特定的實例,與這些函數發生聯系。一旦一個代理包裝了一個或多個函數,你就能通過代理來調用這些函數,而無須事先了解包裝了哪些函數。
請看例1中的代碼,在標號1中,定義一個代理類型Del,由於使用了上下文關鍵字delegate,所以有點像函數的聲明,但與函數聲明不同的是,此處聲明的是一個代理類型Del的實例,其可包裝進任意接受一個int類型作為參數並返回一個int值類型的函數(任意有效的參數列表及返回類型組合都是允許的)。一旦定義了某種代理類型,它只能被用於包裝具有同樣類型的函數;代理類型可被定義在源文件中或命名空間的范圍內,也能定義在類中,並可有public或private訪問控制屬性。
例1:
using namespace System;
ref struct A
{
static int Square(int i)
{
return i * i;
}
};
ref struct B
{
int Cube(int i)
{
return i * i * i;
}
};
/*1*/
delegate int Del(int value);
int main()
{
/*2*/ Del^ d = gcnew Del(&A::Square);
/*3*/ Console::WriteLine("d(10) result = {0}", d(10));
/*4*/ B^ b = gcnew B;
/*5*/ d = gcnew Del(b, &B::Cube);
/*6*/ Console::WriteLine("d(10) result = {0}", d(10));
}
靜態函數A::Square與實例函數B::Cube對Del來說,都具有相同的參數類型及返回類型,因此它們能被包裝進同類型的代理中。注意,即使兩個函數均為public,當考慮它們與Del的兼容性時,它們的可訪問性也是不相關的,這樣的函數也能被定義在相同或不同的類中,主要由程序員來選擇。
一旦定義了某種代理類型,就可創建此類型實例的句柄,並進行初始化或賦值操作,如標號2中所示的靜態函數A::Square,及標號5中所示的實例函數B::Cube。(此處只是出於演示的目的,否則把Cube做成實例函數沒有任何好處。)
創建一個代理實例涉及到調用一個構造函數,如果是在包裝一個靜態函數,只需傳遞進一個指向成員函數的指針;而對實例函數而言,必須傳遞兩個參數:一個實例的句柄及指向實例成員函數的指針。
在初始化代理實例之後,就能間接地調用它們包裝的函數了,用法與直接調用原函數一樣,只不過現在用的是代理實例名,如標號3與6,由包裝函數返回的值也是像直接調用函數時那樣獲得。如果一個代理實例的值為nullptr,此時再試圖調用被包裝的函數,會導致System::NullReferenceException類型異常。
以下是輸出:
d(10) result = 100
d(10) result = 1000
傳遞與返回代理
有時,把包裝好的函數傳遞給另一個函數,會非常有用,接受一方的函數並不知道會傳遞過來哪個函數,並且它也無須關心,只需簡單地通過包裝好的代理,間接調用此函數就行了。
下面以集合中元素排序來說明,大多數時候,集合中元素排序所依據的規則,只在對某對元素進行比較的方法上存在區別。如果在運行時提供進行比較的函數,一個排序過程就能用相應定義的比較函數排出任意的順序,請看例2。