C#委託?這篇文章讓你困惑全擺脫!
作者 | 羽生結弦
責編 | 胡雪蕊
出品 | CSDN(CSDNnews)
在C#中的委託關鍵字是 Delegate,委託類似於C/C 中函數的指針。是存有對某個方法引用的引用類型變數,可在運行時被改變。一般用於實現事件和回調方法。
注意:所有的委託都派生自 System.Delegate 類委託分為 委託類型和委託實例,下面分別進行講解。
零、委託類型和委託實例
1. 委託類型
委託類型定義了委託實例可以調用的方法、方法的返回類型和參數。我們可以通過委託類型的返回類型和參數來得知具體可以調用哪種方法。下面我們通過一個例子來看一下:
(1)首先我們定義一個委託類型:
csharp
delegatestringDemoDelegate(intnum);
(2)其次我們定義兩個方法:
csharp
stringIntToString(intnum)
{
returnnum.ToString();
}
intStringToInt(stringnum)
{
returnint.Parse(num);
}
我們來分析一下這兩個代碼段。首先我們定義了一個委託 DemoDelegate ,委託所定義的返回值類型是 string 類型,參數只包含一個,參數類型是 int。因此根據委託定義得知只有方法的返回值類型是 string 且參數只有一個,並且 參數類型是 int 時,委託才能調用。所以符合條件的方法就只有IntToString。
2. 委託實例
當把方法賦值給委託變數的時候就創建了委託實例。同樣我們用一個例子來看一下:
csharp
staticvoidMain(string[] args)
{
DemoDelegate dd = IntToString;
stringnum = dd(123);
// 將輸出 string 類型 "123"
Console.WriteLine(num);
}
委託實例本質上就是調用者委託委託方法調用被調用者,在這裡就是 Main 方法委託DemoDelegate 去調用 IntToString 方法。這樣做的好處是調用者和被調用者的耦合度降低了。
小知識:上面的代碼我們還可以這樣寫,這兩種寫法是等價的:
csharp
staticvoidMain(string[] args)
{
DemoDelegate dd =newDemoDelegate(IntToString);
stringnum = dd.Invoke(123);
// 將輸出 string 類型 "123"
Console.WriteLine(num);
}
委託的用途很多,我們這裡來看一個例子,這個例子展示了委託其中一種的用途
csharp
publicdelegateintDemoDelegate(intnum);
classTool
{
publicstaticvoidIntSquare(int[] intArray, DemoDelegate dd)
{
for(inti =; i
{
intArray[i] = dd(intArray[i]);
}
}
}
classProgram
{
staticvoidMain(string[] args)
{
DemoDelegate dd = Square;
int[] intArray = {2,4,6};
Tool.IntSquare(intArray, dd);
for(inti =; i
{
Console.WriteLine(intArray[i]);
}
Console.Read();
}
staticintSquare(intnum)
{
returnnum * num;
}
}
我們將委託提取出來,作為一個公共的,然後定義一個 Tool 類,其中定義了一個計算數組中每個值的方法,這個方法接受兩個參數,一個是int類型的數組,另一個是 DemoDelegate 委託類型的參數。通過委託調用 Program 類中的 Square 方法來計算數組中每個數字的平方值。我們在 Main方法中將 Square 方法賦值給委託變數,然後見數組和委託變數一同傳入剛才我們定義的 Tool 類中的 IntSquare 方法,最後輸出值為:4、16、36。這種用途叫做編寫插件式方法,插件式方法就是只有在運行時才將方法賦值給委託。
多播委託
前面的例子我們都是講一個方法賦值給委託變數,這種叫單播委託。但是在大部分情況下我們需要將多個方法賦值給委託,這是我們就用到了多播委託。要把多個方法賦值給委託變數,我們需要用到 和 = ,方法如下:
csharp
Delegate d = method1;
d = method2;
當我們調用委託 d 的時候,就會按照賦值順序來調用方法,即先調用 method1 再調用 method2 。我們有時候也需要移除委託中的某個方法,這時我們可以用 - 和 -= 進行操作,比如我們移除前面例子中的 method1 方法:
csharp
d -= method1;
當我們進行 或者 = 操作時,操作數可以是null,相當於把一個新值賦值給了委託變數,也就是說如下兩種方法是等價的:
方法一:
csharp
Delegate d = null;
d = method1;
方法二:
csharp
d = method1;
同理,當進行 - 或者 -= 操作時,相當於把null值賦給了委託變數。
下面我們來看一下多播委託的例子:
csharp
public delegateintDemoDelegate(intnum);
staticvoidMain(string[] args)
{
DemoDelegate dd =null;
dd = Square;
dd = Remainder;
dd(5);
Console.Read();
}
staticintSquare(intnum)
{
Console.WriteLine(num*num);
returnnum*num;
}
staticintRemainder(intnum)
{
Console.WriteLine(num%2);
returnnum%2;
}
在代碼中我們定義了兩個方法,分別是計算數值平方的 Square 和計算數值除以2的餘數Remainder 。在 Main 方法中我們利用 = 將兩個方法賦值給委託變數 dd 。執行這段代碼,最終輸出結果為:25、1。當我們利用 - 或者 -= 來移除掉一個方法時,例如移除掉 Square ,這時就只會輸出1,當我們把所有的方法都移除掉時,程序運行起來將會報空指針異常的錯誤。
注意:
1. 委託不可變,使用 ** =** 或者 **-=** 實際上是創建了新的委託實例,並把它付給當前的委託變數。
2. 如果多播委託的返回類型不是void,那麼調用者只能獲取到最後一個被調用方法的返回值,前面方法的返回值將會被拋棄。
3. c#會將 、-、 =、-=編譯為 Combine 和 Remove兩個方法。
實例方法委託和靜態方法委託
實例方法和靜態方法都是c#中經常用到的方法,我們可以將這兩種方法都賦值給委託,因此就出現了實例方法穩妥和靜態方法委託。它們之間的區別如下:1. 一個實例方法被賦值給委託對象時,委託對象不僅要保留對方法的引用,還要保留方法所屬實例的引用,這時 System.Delegate 中的Target 屬性就表示的是方法所屬的實例;
2. 一個靜態方法賦值給委託對象時,Target 屬性值為null。例子如下:- 首先定義一個類 Demo 裡邊包含 NumAdd 實例方法和 Num 靜態方法
csharp
classDemo
{
publicintNumAdd(intnum)
{
return num;
}
publicstaticintNum(intnum)
{
returnnum;
}
}
接著在控制台中調用這兩個方法
csharp
publicdelegateintDemoDelegate(intnum);
classProgram
{
staticvoidMain(string[] args)
{
Demo demo =newDemo();
DemoDelegate dd = demo.NumAdd;
dd(2);
Console.WriteLine("方法所屬實例:" dd.Target);
Console.WriteLine("調用方法:" dd.Method);
DemoDelegate staticDd = Demo.Num;
staticDd(2);
Console.WriteLine("方法所屬實例:" staticDd.Target);
Console.WriteLine("調用方法:" staticDd.Method);
Console.ReadLine();
}
}
運行以上代碼,輸出結果如下:
我們可以看到,將靜態方法賦值給委託對象後列印方法所屬實例為空。
泛型委託類型
在一些情況下我們不確定參數類型和返回值類型,這時我們就需要用到泛型委託類型,語法如下:
csharp
public delegateTDemoDelegate(Targ);
我們具體看一下例子:
csharp
publicdelegateT DemoDelegate(T num);
classDemo
{
publicintNumAdd(intnum)
{
return num;
}
}
classProgram
{
staticvoidMain(string[] args)
{
Demo demo =newDemo();
DemoDelegate dd = demo.NumAdd;
Console.WriteLine(dd(2));
Console.ReadLine();
}
}
運行上面的代碼,控制台將會輸出結果 3
注意:我們可以將返回值類型或者參數類型固定,例如:
csharp
publicdelegatestringDemoDelegate(T arg);
publicdelegateT DemoDelegate(intarg);
使用泛型委託的好處是可以寫出一組委託類型,這組方法可以擁有熱議類型的返回值和任意數量的參數。下一小節我們就來看一下具體怎麼用。
Action 和 Func
1. Func
Func是一個具有返回類型的方法,它的類型參數數量可以多達16個,其中包括0到多個輸入類型參數和一個輸出類型參數。下面的代碼段展示了Func部分類型參數:
csharp
delegatevoidAction();
delegatevoidAction (T t);
delegatevoidAction(inT1,inT2)(T t1,T t2);
2. Action
Action 是一個不具有返回類型的方法,他的類型參數數量同樣多達16個。下面展示了部分Action類型參數:
csharp
delegatevoidAction();
delegatevoidAction (T t);
delegatevoidAction(inT1,inT2)(T t1,T t2);
我們來看一下例子,以Func為例,Action同理
csharp
classDemo
{
publicvoidNum(T[]array, Func func)
{
for(inti =; i
{
Console.WriteLine(func(array[i]));
}
}
}
classProgram
{
staticvoidMain(string[] args)
{
Demo demo =newDemo();
int[]array=newint[] {2,4,6};
demo.Num(array, NumAdd);
Console.ReadLine();
}
staticintNumAdd(intnum)
{
return num;
}
}
從代碼中可以看出,我們將 Demo 類中的 Num 方法的第二個參數類型寫成了 Func,這裡的意思是委託實例的返回類型和類型參數都是T類型。我們在Main函數中通過委託,控制台輸出結果是3、5、7 。這時我們就看出了使用 Func 和 Action 的優點了,我們不需要在外部顯式的定義委託,比較方便。
冷知識
1. 委託與介面
一般來說介面可以解決的問題,委託同樣也可以解決,那麼什麼時候使用委託呢?我們來看一下:
(1)當需要多播的時候;
(2)訂閱者需要多次實現介面的時候。
2. 委託兼容性
(1)委託類型
委託類型之間互不兼容,即使它們的簽名一樣也不行,也就是說如下的寫法是錯誤的。
csharp
delegatevoidDD1();
delegatevoidDD2();
DD1 dd1=Method;
DD2 dd2=dd1;
(2)委託實例
如果委託實例具有相同的方法目標,那麼委託實例就是相等的。
(3)參數
當調用一個方法時,提供的參數可以比方法參數更具體。例如被調用的方法參數是 Object類型,但是提供的參數是 String 類型,這時程序不會報錯,因為string 來自 object,string 比 object 更具體。(委託只支持引用轉換)
(4)返回值
同參數一樣,當調用方法時,可以獲得一個比被調用方法返回值更具體的返回值。
作者簡介:朱鋼,筆名羽生結弦,CSDN博客專家,.NET高級開發工程師,7年一線開發經驗,參與過電子政務系統和AI客服系統的開發,以及互聯網招聘網站的架構設計,目前就職於北京恆創融慧科技發展有限公司,從事企業級安全監控系統的開發。
【End】
5G進入元年,物聯網發展愈加火爆!
你是否身懷絕技、卻無人知曉;別讓你的IoT項目再默默無聞了!
繼第一屆AI優秀案例評選活動之後,2019年案例評選活動再度升級,CSDN將評選出TOP 30優秀IoT案例,趕快掃碼參與評選吧!重磅福利,等你來領!
熱 文推 薦


※產品小姐姐收到這個黑科技後,開心了一整天……
※Python 揭秘斐波那契定律,如何幫助碼農分析股票?| 技術頭條
TAG:CSDN |