委托
- 如果我們要把方法當(dāng)做參數(shù)來傳遞,就用到委托,簡單來說,委托是一個類型,這個類型可以賦值一個方法的引用。
聲明委托
在c#中使用一個類分兩個階段,首先定義這個類,告訴編輯器這個類由什么字段和方法組成,然后使用這個類實例化對象。在我們使用委托的時候,也需要經(jīng)過這兩個階段,首先定義委托,告訴編輯器我們這個委托可以指向那些類型的方法,然后,創(chuàng)建該委托的實例。
-
定義委托的方法如下:
delegate void IntMethodInvoker(int x);定義一個委托:
IntMethodInvoker,這個委托可以指向什么類型的方法?這個方法要帶有一個
int類型的參數(shù),并且方法的返回值是void,定義一個委托要定義方法的參數(shù)和返回值,使用關(guān)鍵字delegate定義。
-
定義案例:
delegate double TwoLongOp(long frist, long second);delegate string GetAString();
委托使用
private delegate string GetAString();
static void Main(){
int x = 40;
GetAString firstStringMethod = new GetAString(x.ToString);
Console.WriteLine(firstStringMethod());
}
在這里我們首先使用
GetAString委托聲明了一個類型叫做fristStringMethod,接下來使用new對它進行初始化,使它引用到x中的ToString方法上,這樣firstStringMethod就相當(dāng)于x.ToString,我們通過firstStringMethod()執(zhí)行方法就相當(dāng)于x.ToString()-
通過委托示例調(diào)用方法有兩種方式:
fristStringMethod();firstStringMethod.Invoke();
委托的賦值
GetAString firstStringMethod = new GetAString(x.ToString);- 只需要把方法名給一個委托的構(gòu)造方法就可以了
GetAString firstStringMethod = x.ToString; - 也可以把方法名直接給委托的實例
簡單委托示例
- 定義一個類MathsOperations里面有兩個靜態(tài)方法,使用委托調(diào)用該方法
class MathOperations{
public static double MultiplyByTwo(double value){
return value*2;
}
public static double Square(double value){
return value*value;
}
}
delegate double DoubleOp(double x);
static void Main(){
DoubleOp[] operations={ MathOperations.MultiplyByTwo,MathOperations.Square };
for(int i =0;i<operations.Length;i++){
Console.WriteLine("Using operations "+i);
ProcessAndDisplayNumber( operations[i],2.0 );
}
}
static void ProcessAndDisplayNumber(DoubleOp action,double value){
double res = action(value);
Console.Writeline("Value :"+value+" Result:"+res);
}
Action委托和Func委托
- 除了我們自己定義的委托之外,系統(tǒng)還給我們提供過來一個內(nèi)置的委托類型
-
Action和Func -
Action委托引用了一個void返回類型的方法,T表示方法參數(shù) - 先看
Action委托有哪些- Action
- Action<in T>
- Action<in T1,in T2>
- Action<in T1,in T2 .... inT16>
-
Func引用了一個帶有一個返回值的方法,它可以傳遞0或者多到16個參數(shù)類型,和一個返回類型- Func<out TResult>
- Func<in T,out TResult>
- Func<int T1,inT2,,,,,,in T16,out TResult>
案例1
delegate double DoubleOp(double x);
如何用Func表示Func<double,double>
案例2-對int類型排序
- 對集合進行排序,冒泡排序
bool swapped = true;
do{
swapped = false;
for(int i =0;i<sortArray.Length -1;i++){
if(sortArray[i]>sortArray[i+1]){
int temp= sortArray[i];
sortArray[i]=sortArray[i+1];
sortArray[i+1]=temp;
swapped = true;
}
}
}while(swapped);
- 這里的冒泡排序只適用于int類型的,如果我們想對他進行擴展,這樣它就可以給任何對象排序。
案例2-雇員類
class Employee{
public Employ(string name,decimal salary){
this.Name = name;
this.Salary = salary;
}
public string Name{get;private set;}
public decimal Salary{get;private set;}
public static bool CompareSalary(Employee e1,Employee e2){
return e1.salary>e2.salary;
}
}
案例2-通用的排序方法
public static void Sort<T>( List<T> sortArray,Func<T,T,bool> comparision ){
bool swapped = true;
do{
swapped = false;
for(int i=0;i<sortArray.Count-1;i++){
if(comparision(sortArray[i+1],sortArray[i])){
T temp = sortArray[i];
sortArray[i]=sortArray[i+1];
sortArray[i+1]=temp;
swapped = true;
}
}
}while(swapped);
}
案例2-對雇員類排序
static void Main(){
Employee[] employees = {
new Employee("Bunny",20000),
new Employee("Bunny",10000),
new Employee("Bunny",25000),
new Employee("Bunny",100000),
new Employee("Bunny",23000),
new Employee("Bunny",50000),
};
Sort(employees,Employee.CompareSalary);
// 輸出
}
多播委托
-
前面使用的委托都只包含一個方法的調(diào)用,但是委托也可以包含多個方法,這種委托叫做多播委托。使用多播委托就可以按照順序調(diào)用多個方法,多播委托只能得到調(diào)用的最后一個方法的結(jié)果,一般我們把多播委托的返回類型聲明為
void。Action action1 = Test1;action2+=Test2;action2-=Test1;
多播委托包含一個逐個調(diào)用的委托集合,如果通過委托調(diào)用的其中一個方法拋出異常,整個迭代就會停止。
取得多播委托中所有方法的委托:
Action a1 = Method1;
a1+=Method2;
Delegate[] delegates=a1.GetInvocationList();
foreach(delegate d in delegates){
//d();
d.DynamicInvoke(null);
}
// 遍歷多播委托中所有的委托,然后單獨調(diào)用
匿名方法
- 到目前為止,使用委托,都是先定義一個方法,然后把方法給委托的實例。但還有另外一種使用委托的方式,不用去定義一個方法,應(yīng)該說是使用匿名方法(方法沒有名字)。
Func<int,int,int> plus = delegate (int a,int b){
int temp = a+b;
return temp;
};
int res = plus(34,34);
Console.WriteLine(res);
- 在這里相當(dāng)于直接把要引用的方法直接寫在了后面,優(yōu)點是減少了要編寫的代碼,減少代碼的復(fù)雜性
Lambda表達式-表示一個方法的定義
- 從C#3.0開始,可以使用Lambda表達式代替匿名方法。只要有委托參數(shù)類型的地方就可以使用Lambda表達式。剛剛的例子可以修改為
Func<int,int,int> plus = (a,b)=>{ int temp= a+b;return temp; };
int res = plus(34,34);
Console.WriteLine(res);
- Lambda運算符“=>”的左邊列出了需要的參數(shù),如果是一個參數(shù)可以直接寫 a=>(參數(shù)名自己定義),如果多個參數(shù)就使用括號括起來,參數(shù)之間以,間隔
多行語句
- 如果Lambda表達式只有一條語句,在方法快內(nèi)就不需要花括號和return語句,編譯器會自動添加return語句
Func<double,double> square = x=>x*x;
// 添加花括號,return語句和分號是完全合法的
Func<double,double> square = x=>{
return x*x;
}
- 如果Lambda表達式的實現(xiàn)代碼中需要多條語句,就必須添加花括號和return語句。
Lambda表達式外部的變量
- 通過Lambda表達式可以訪問Lambda表達式塊外部的變量。這是一個非常好的功能,但如果不能正確使用,也會非常危險。示例:
int somVal = 5;
Func<int,int> f = x=>x+somVal;
Console.WriteLine(f(3));//8
somVal = 7;
Console.WriteLine(f(3));//10
- 這個方法的結(jié)果,不但受到參數(shù)的控制,還受到somVal變量的控制,結(jié)果不可控,容易出現(xiàn)編程問題,用的時候要謹(jǐn)慎。
事件
- 事件(event)基于委托,為委托提供了一個發(fā)布/訂閱機制,我們可以說事件是一種具有特殊簽名的委托。
什么是事件?
事件(Event)是類或?qū)ο笙蚱渌惢驅(qū)ο笸ㄖl(fā)生的事情的一種特殊簽名的委托.
事件的聲明
public event 委托類型 事件名;
事件使用event關(guān)鍵詞來聲明,他的返回類值是一個委托類型。
通常事件的命名,以名字+Event 作為他的名稱,在編碼中盡量使用規(guī)范命名,增加代碼可讀性。
-
為了更加容易理解事件,我們還是以前面的動物的示例來說明,有三只動物,貓(名叫Tom),還有兩只老鼠(Jerry和Jack),當(dāng)貓叫的時候,觸發(fā)事件(CatShout),然后兩只老鼠開始逃跑(MouseRun)。接下來用代碼來實現(xiàn)。(設(shè)計模式-觀察者模式)
_15248252634703.png
Cat類和Mouse類
class Cat
{
string catName;
string catColor { get; set; }
public Cat(string name, string color)
{
this.catName = name;
catColor = color;
}
public void CatShout()
{
Console.WriteLine(catColor+" 的貓 "+catName+" 過來了,喵!喵!喵!\n");
//貓叫時觸發(fā)事件
//貓叫時,如果CatShoutEvent中有登記事件,則執(zhí)行該事件
if (CatShoutEvent != null)
CatShoutEvent();
}
public delegate void CatShoutEventHandler();
public event CatShoutEventHandler CatShoutEvent;
}
class Mouse
{
string mouseName;
string mouseColor { get; set; }
public Mouse(string name, string color)
{
this.mouseName = name;
this.mouseColor = color;
}
public void MouseRun()
{
Console.WriteLine(mouseColor + " 的老鼠 " + mouseName + " 說:\"老貓來了,快跑!\" \n我跑??!\n我使勁跑!!\n我加速使勁跑?。?!\n");
}
}
運行代碼結(jié)果
Console.WriteLine("[場景說明]: 一個月明星稀的午夜,有兩只老鼠在偷油吃\n");
Mouse Jerry = new Mouse("Jerry", "白色");
Mouse Jack = new Mouse("Jack", "黃色");
Console.WriteLine("[場景說明]: 一只黑貓躡手躡腳的走了過來\n");
Cat Tom = new Cat("Tom", "黑色");
Console.WriteLine("[場景說明]: 為了安全的偷油,登記了一個貓叫的事件\n");
Tom.CatShoutEvent += new Cat.CatShoutEventHandler(Jerry.MouseRun);
Tom.CatShoutEvent += new Cat.CatShoutEventHandler(Jack.MouseRun);
Console.WriteLine("[場景說明]: 貓叫了三聲\n");
Tom.CatShout();
Console.ReadKey();
事件與委托的聯(lián)系和區(qū)別
事件是一種特殊的委托,或者說是受限制的委托,是委托一種特殊應(yīng)用,只能施加+=,-=操作符。二者本質(zhì)上是一個東西。
event ActionHandler Tick;// 編譯成創(chuàng)建一個私有的委托示例, 和施加在其上的add, remove方法.event只允許用add, remove方法來操作,這導(dǎo)致了它不允許在類的外部被直接觸發(fā),只能在類的內(nèi)部適合的時機觸發(fā)。委托可以在外部被觸發(fā),但是別這么用。
使用中,委托常用來表達回調(diào),事件表達外發(fā)的接口。
委托和事件支持靜態(tài)方法和成員方法, delegate(void * pthis, f_ptr), 支持靜態(tài)返方法時, pthis傳null.支持成員方法時, pthis傳被通知的對象.
委托對象里的三個重要字段是, pthis, f_ptr, pnext, 也就是被通知對象引用, 函數(shù)指針/地址, 委托鏈表的下一個委托節(jié)點.
