1. 程式人生 > >委託的初步理解和用法

委託的初步理解和用法

委託是用來處理其他語言需要用函式指標來處理的情況的。

都說C#沒有指標,而委託就相當於是個函式指標,不過也有很多不同點,委託是完全面向物件的。

C++中指標僅指向成員函式,委託同時封裝了物件例項和方法。

C#的叫法

函式:方法

返回值+引數列表:簽名

把它和函式指標放一起說可能會比較好理解委託的作用。

首先,委託是個類,委託宣告定義一個System.Delegate類派生的類

委託是可以儲存對方法的引用的類。與其他的類不同,委託類具有一個簽名,並且它只能對與其簽名匹配的方法進行引用。這樣,委託就等效於一個型別安全函式指標。

委託例項(也就是類的物件)封裝了一個呼叫列表,該列表列出了一個或多個方法,每個方法稱為一個可呼叫實體。

可以說委託物件可以指向很多個其他類的方法,或者說委託這個時候相當於一個函式指標,可以指向其他類的一個或多個成員函式。

當然委託物件所指向的方法的簽名,一定要是跟宣告委託時的簽名一樣。也就是返回值和引數列表要一樣。

Ø  委託的宣告

[委託修飾符] delegate 返回值型別委託名([形參列表]);

藍色部分就是該委託的簽名

委託宣告的語法與一般C#類的宣告語法不一致。C#編譯器會根據委託的宣告語法,自動建立一個派生於System.MulticastDelegat的類及其相關實現細節。

簡單說,上面的宣告在編譯的時候,實際上會轉換成一個類,類的成員函式自動生成。

Ø  委託的例項化和呼叫

聲明瞭委託(實際上是個派生類),需要建立委託的例項(物件),然後呼叫其方法(函式成員)

建立委託例項的基本形式如下:

委託名委託例項名 = new 委託名(匹配方法);

委託名委託例項名 = 匹配方法 ;   //等價簡寫

委託例項名的同步呼叫與方法的呼叫:

委託例項名.Invoke(實參列表);

委託例項名(實參列表);

簡單看個例子理解委託的例項化和呼叫        

using System;

namespaceCSharpBook.Chapter09

{

delegatevoidD(int x);       // 宣告委託

classC

{

publicstaticvoid M1(

int i){Console.WriteLine("C.M1:" + i);}

publicstaticvoid M2(int i){Console.WriteLine("C.M2:" + i);}

publicvoid M3(int i){Console.WriteLine("C.M3:" + i);}

}

classTest

{

staticvoid Main()

{

  D d1 = newD(C.M1);  //使用new關鍵字,建立委託物件,指向類靜態方法

d1(-1);              //呼叫M1

D d2 = C.M2;        //使用賦值運算子,建立委託物件,指向類靜態方法

d2(-2);             //呼叫M2

C objc = newC();

D d3 = newD(objc.M3); //使用new關鍵字,建立委託物件,指向物件例項方法

d2(-3);              //呼叫M3

Console.ReadKey();

}

}

}

再看個稍微複雜點的

using System;

namespaceCSharpBook.Chapter09

{

delegatevoidD(int[] A);   // 宣告委託

classArraySort

{  

publicstaticvoid DisplayArray(int[] A) //列印陣列

foreach (int i in A) Console.Write("{0,5} ", i); Console.WriteLine(); }

publicstaticvoid GeneralSort(int[] A, D sort)

{  //通用排序程式

sort(A);  // 呼叫排序演算法,委託例項名(實參列表);

Console.WriteLine("升序陣列: "); DisplayArray(A); //顯示陣列

}

publicstaticvoid BubbleSort(int[] A)

{  //冒泡演算法

int i, t;

int N = A.Length;   //獲取陣列A的長度N

for (int loop = 1; loop <=N - 1; loop++)//外迴圈進行N-1輪比較

{  for (i = 0; i <= N -1 - loop; i++) //內迴圈兩兩比較,大數下沉

if (A[i] > A[i +1])       //相鄰兩數交換

{ t = A[i]; A[i] = A[i + 1];A[i + 1] = t; }

}

}

publicstaticvoid SelectSort(int[] A)

{  //選擇演算法

int i, t, MinI;

int N = A.Length;  //獲取陣列A的長度N

for (int loop = 0; loop<= N - 2; loop++)//外迴圈進行N-1輪比較

{ MinI = loop;

for (i = loop; i <=N - 1; i++) //內迴圈中在無序數中找最小值

if (A[i] < A[MinI])MinI = i;

t = A[loop]; A[loop] = A[MinI];A[MinI] = t;//最小值與第一個元素交換

}

}

staticvoid Main()

{  int[] A = newint[10];  Random rNum = newRandom();

//陣列A賦值(0~100之間的隨機數)

for (int i = 0; i <A.Length; i++) A[i] = rNum.Next(101);

Console.WriteLine("原始陣列: "); DisplayArray(A); //顯示陣列

D d1 = newD(ArraySort.BubbleSort);//建立委託例項,指向冒泡演算法

Console.Write("冒泡演算法---"); GeneralSort(A, d1);

D d2 = newD(ArraySort.SelectSort); //建立委託例項,指向選擇演算法

Console.Write("選擇演算法---"); GeneralSort(A, d2); Console.ReadKey();

}

}

}

Ø  匿名委託

匿名方法。即無須先宣告類或結構體以及與委託匹配的方法,而是在建立委託的例項時,直接宣告與委託匹配的方法的程式碼塊。

簡單說,就是把委託例項所指向的方法直接寫在這個委託下面,所以不需要單獨宣告這個所指向的方法。

宣告匿名委託的基本語法為:

委託名委託例項名 = new delegate [形參列表]

{

方法體;

}

例如

staticvoid Main()

// 使用匿名方法例項化delegate

Printer p = delegate(string j)

        {

Console.WriteLine(j);

   };

p("使用匿名方法的委託的呼叫。"); //匿名delegate呼叫結果

}

Ø  多播委託

個人認為多播委託也是跟函式指標有所區別的一個地方

函式指標指向的是一個記憶體單元,這個單元指向一個函式。而多播委託就是可以讓一個委託物件指向多個函式。

多播委託包括Combine RemoveRemoveAll三個靜態方法,用於新增、刪除指向的函式到呼叫列表。也可以用+或+=  -或-= 進行新增、刪除

委託類 D

建立委託例項d1 d2 d3 d4...假設每個委託例項指向的函式不一樣,但簽名一定要一樣。

d1+=d2;

那麼呼叫d1的時候就會依次呼叫d1d2。每個指向的函式函式的形參列表也都一樣。

<委託型別> <例項化名>+=new <委託型別>(<註冊函式>)例子:CheckDelegate _checkDelegate=new CheckDelegate(CheckMod);//將函式CheckMod註冊到委託例項_checkDelegate也可以直接將匹配的函式註冊到例項化委託:
<委託型別> <例項化名>+=<註冊函式>
例子:CheckDelegate _checkDelegate+=CheckMod;//將函式CheckMod註冊到委託例項_checkDelegate

另外有一點需要注意的是,如果對註冊了函式的委託例項從新使用=號賦值,相當於是重新例項化了委託,之前在上面註冊的函式和委託例項之間也不再產生任何關係

Ø  委託的非同步呼叫

//沒怎麼看懂,先跳過。。。

Ø  委託的相容性

如果硬性要求委託的簽名和所匹配的方法的簽名完全一樣(返回值型別一樣,引數列表一樣),用起來就會很侷限。

也就是說,委託的簽名和所匹配的方法的簽名也可以不完全一樣,簡單來說需要滿足以下條件:(假設委託型別D,方法M)

1.      D 和M的引數數目相同,且各自對應引數具有相同的ref或out修飾符;

2.      對於每個ref或out引數,D中的引數型別與M中的引數型別相同;

3.      存在從M的返回型別到D的返回型別的隱式引用轉換(協變);

4.      每一個值引數(非refout修飾符的引數)都存在從D中的引數型別到M中的對應引數型別的隱式引用轉換(逆變)

舉個例子,

現在有People類,派生出Student類

方法 : Student M(string name);

委託簽名 :delegate People D(string name);

Student可以隱式轉換成People

方法:void M(People p);

委託簽名:delegate void D(Student s

D的委託例項一樣可以指向方法M,相容

所謂隱式轉換,就是系統預設的轉換,其本質是小儲存容量資料型別自動轉換為大儲存容量資料型別。 

有如下幾種: 

從sbyte型別到short,int,long,float,double,或decimal型別。 

從byte型別到short,ushort,int,uint,long,ulong,float,double,或decimal型別。

從short型別到int,long,float,double,或decimal型別。 

從ushort型別到int,uint,long,ulong,float,double,或decimal型別。

從int型別到long,float,double,或decimal型別。 從uint型別到long,ulong,float,double,或decimal型別。 

從long型別到float,double,或decimal型別。 從ulong型別到float,double,或decimal型別。 

從char型別到ushort,int,uint,long,ulong,float,double,或decimal型別。 從float型別到double型別。

//以上只是委託的基本概念和用法,與事件結合後使用會比較靈活。

//還有泛型委託和泛型事件。。。