委託是用來處理其他語言需要用函數指針來處理的情況的。
都說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的時候就會依次調用d1、d2。每個指向的函數函數的形參列表也都一樣。
<委託類型> <實例化名>+=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. 每一個值參數(非ref或out修飾符的參數)都存在從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類型。
//以上只是委託的基本概念和用法,與事件結合後使用會比較靈活。
//還有泛型委託和泛型事件。。。