1.什么是委托?(方法作另一個方法的參數)
delegate void MyDel(int value); //聲明委托類型
和類一樣,委托是用戶自定義的類型,但是類是數據和方法的集合,而委托是持有一個或多個方法。
delegate可以看做是一個包含有序方法列表的對象,這些方法具有相同的簽名和返回類型。
委托保存的方法可以來自任何類或結構
調用列表中的方法可以是實例方法也可以是靜態方法
調用委托時,會執行調用列表中的所有方法
2.聲明委托類型
delegate void MyDel (int value);委托的是無返回值,且單一int參數的方法
關鍵字 返回類型 委托類簽名 簽名
3.創建委托對象
MyDel delVar;
委托類型 變量
delVar = new MyDel(myInstObj.MyM1); 或 delVar = myInstObj.MyM1;
delVar = new MyDel(sClass.otherM2); 或 delVar = sClass.otherM2;
4.組合委托
MyDel a = myInstObj.myM1;
MyDel b = SClass.OtherM2;
MyDel c = a + b;
5.為委托添加方法+= 同理刪除方法: -=
MyDel delVar = inst.myM1; //創建並初始化
delVar += Scl.m3;
delVar += X.Act;
6.調用委托(從底部函數開始執行)
MyDel delVar = inst.MyM1;
delVar(55);
7.調用帶返回值的委托
最后一個方法的返回值就是委托調用返回的值,其他方法的返回值會被忽略
8.調用帶引用參數的委托
參數值會根據調用列表中的一個或多個方法的返回值而改變
9.匿名方法
在初始化委托時內聯聲明的方法
delegate (Parameters) {ImplementationCode}
關鍵字 參數列表 語句塊
delegate int OtherDel(int Inparam);
static void Main()
{
OtherDel del = delegate(int x)
{
return x + 20;
};
}
為什么使用委托:在我們編寫程序的時候,程序的上下文是固定的,在某個關鍵的部分,不確定調用哪個函數,這時候需要讓程序更換被調用函數,又不想兩個函數耦合性太高,使用委托可實現間接調用,或者可替換調用
什么時候該用委托,為什么要用委托,委托有什么好處
我用的最多的是在多線程 訪問UI界面控件的時候。
UI控件都由主線程創建和繪制的,如果子線程想訪問控件的話,就把這個任務委托給主線程。
比如有個子線程 想要給 form中的label控件賦值 Label.Text="內容"
此時必須用委托讓主線程去執行這個賦值語句。
如果不這么做,會出現線程安全的異常。產生多個線程同時訪問同一個控件的異常。這是不符合線程安全的要求的
本篇文章主要介紹委托的應用。
委托是大家最常見的語法了,但會用與精通之間的差別是巨大的。
一個程序員如果不能精通委托,那么,他永遠無法成為高級程序員。
所以,讓我們把委托刻到血液里吧。
這樣,你才能稱為[Developer]。
委托的定義
什么是委托?
委托實際上是一種類型,是一種引用類型。
微軟用delegate關鍵字來聲明委托,delegate與int,string,double等關鍵字一樣。都是聲明用的。
下面先看下聲明代碼,這里聲明了兩個委托。
1
2
|
public
delegate
void
TestDelegate(string message);
public
delegate
int
TestDelegate(MyType m,
long
num);
|
delegate既然是關鍵字,和int,string一樣,那么,為什么delegate后又跟了一個void或者int呢?
如果他們是同等地位的關鍵字,為什么可以一起使用呢?
很簡單,我們把delegate后面的 【void TestDelegate(string message)】理解為一個變量,是不是就清晰明了了一些。
我們把delegate關鍵字理解為,是用來專門來定義這種復雜的變量的。而這種復雜的變量可以包含一個返回值和任意數目任意類型的傳入參數。
有沒有感覺,這個復雜的變量特別像一個函數的定義。
沒錯,官方定義,委托類型的聲明與方法簽名相似。所以,這個復雜變量,的確,書寫的方式就是與函數一樣。
那么,為什么這個聲明方式如此怪異呢,是因為,我們用delegate定義的變量,只能用函數賦值。賦值方式如下所示:
1
2
3
4
5
6
7
8
9
10
|
public
delegate
void
TestDelegate(
string
message);
public
delegate
long
TestDelegate2(
int
m,
long
num);
public
static
void
Excute()
{
TestDelegate2 td = Double;
}
static
long
Double(
int
m,
long
num)
{
return
m * num;
}
|
委托的基本應用
學會了賦值以后,我開始使用委托。
委托的使用方式如下:
1
2
|
string
result = td(51, 8);
Console.WriteLine(result);
|
這里我們會發現,委托的使用方式與函數調用一樣。
沒錯,它們的確是一樣的。因為委托是用函數來賦值的,所以調用方式一樣也並不奇怪,不是嗎。
換一種說法,就是委托封裝了一個函數。
如果委托是封裝的函數,並且它又是引用類型。那么委托第一種常規的應用就浮現出來了。
那就是——引用類型的函數。
如果函數是引用類型,那么這個函數只要沒被內存回收,就可以被調用。如果是public函數或者是public static函數,那么它能跨越的東西就更多了。
比如可以跨類調用,跨程序集調用等等。而這種用法,就是委托的基本應用。
匿名委托的應用
匿名委托的官方介紹:在 2.0 之前的 C# 版本中,聲明委托的唯一方式是使用命名方法。 C# 2.0 引入匿名方法,在 C# 3.0 及更高版本中,Lambda 表達式取代匿名方法作為編寫內聯代碼的首選方式。
看不懂沒關系,我們直接來學習使用。代碼如下:
1
2
3
4
5
6
|
delegate
string
anonymousDelegate(
int
m,
long
num);
public
static
void
Excute()
{
anonymousDelegate ad =
delegate
(
int
m,
long
num) {
return
m.ToString() + num.ToString(); };
//2.0時代的匿名委托
anonymousDelegate ad2 = (m, num) => {
return
m.ToString() + num.ToString(); };
//3.0以后匿名委托
}
|
如代碼所示,匿名委托是Lambda表達式,不懂的同學就當它是有固定寫法即可,不用講什么道理,只要記住並應用即可。
匿名委托雖然減少了一點代碼,但還是要求我們自己去聲明委托。所有,還能再簡寫一點嗎?
答案當然是,可以的。
Action與Func
Action與Func是微軟為我們預先定義好了的,兩個委托變量。其中Action是不帶返回值的委托,Func是帶返回值的委托。
可以說,Action與Func完全包含了,我們日常使用所需的,全部的,委托變量。
也就是說,我們可以不用再去自己手動聲明委托了。
下面來看最簡單的Action與Func的定義:
1
2
|
Action a1 = () => { };
Func<
int
> f1 = () => {
return
1; };
//必須寫 return 1;
|
Action與Func是泛型委托,各支持16個入參變量。下面代碼為一個入參的定義,多參數以此類推。
1
2
|
Action<
int
> a1 = (i) => { };
Func<
string
,
int
> f1 = (str) => {
return
1;
//必須寫 return 1; };
|
委托的線程應用
委托的線程應用是委托的第二種用法,分為線程使用委托,和委托的異步應用兩種。
我們先看線程使用委托。如下代碼所示,一個無入參匿名Action和一個無入參匿名Func。
1
2
3
4
5
|
Task taskAction =
new
Task(() => { });
//無入參匿名Action
taskAction.Start();
Task<
int
> taskFunc =
new
Task<
int
>(() => {
return
1; });
//無入參匿名Func
taskFunc.Start();
int
result= taskFunc.GetAwaiter().GetResult();
//獲取線程返回結果
|
我們能看到兩種委托應用,代碼都非常簡潔。
下面我們再來看委托的異步應用。首先看最簡單的異步調用。
1
2
3
4
5
6
7
8
9
10
|
Action action =
new
Action(() => { });
IAsyncResult result = action.BeginInvoke((iar) =>
{
},
null
);
Func<
int
> func =
new
Func<
int
>(() => {
return
1; });
IAsyncResult resultfunc = func.BeginInvoke((iar) =>
{
var
res = func.EndInvoke(iar);
},
null
);
|
這里我們使用委托的BeginInvoke方法來開啟線程,進行異步調用。如上面代碼所示,這里介紹了Action與Func的最基礎的異步應用。
委托,架構的血液
委托是架構的血液,如果系統中沒有委托,那代碼將堆疊到一起,比大力膠粘的都緊密。
就好比一碗湯面倒掉了所有的湯,只要它靜放一個陣子,就會變成一坨面球,讓你無從下嘴。
所以,委托是架構的血液,是框架的流暢的基石。
那么委托到底是如何流動的呢?
我們先從剛介紹過的委托的線程應用說起。
----------------------------------------------------------------------------------------------------
第一核心應用——隨手線程:
我們在做開發的時候,一定接觸過父類。父類是干什么的呢?父類通常是用來編寫公共屬性和函數,方便子類調用的。
那我們的委托的第一個核心應用,就是父類的公共函數,線程隨手啟動。如何隨手開啟呢?
首先,我們創建父類代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
class
BaseDelegateSyntax
{
public
void
AsyncLoad(Action action)
{
}
public
void
AsyncLoad(Action action, Action callback)
{
IAsyncResult result = action.BeginInvoke((iar) =>
{
callback();
},
null
);
}
public
void
AsyncLoad<T>(Action<T> action, T para, Action callback)
{
IAsyncResult result = action.BeginInvoke(para, (iar) =>
{
callback();
},
null
);
}
public
void
AsyncLoad<T, R>(Func<T, R> action, T para, Action<R> callback)
{
IAsyncResult result = action.BeginInvoke(para, (iar) =>
{
var
res = action.EndInvoke(iar);
callback(res);
},
null
);
}
}
|
我們看到上面的代碼,父類中添加了四個異步委托的調用函數,接下來,我們就可以在繼承該類的子類中,隨手開啟線程了。
子類代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
class
ChildDelegateSyntax : BaseDelegateSyntax
{
public
void
Excute()
{
//開啟異步方法
base
.AsyncLoad(() => { });
//開啟異步方法,並且在異步結束后,觸發回調方法
base
.AsyncLoad(() => { },
()=>
{
//我是回調方法
});
//開啟異步有入參的方法,傳遞參數,並且在異步結束后,觸發回調方法
base
.AsyncLoad<
string
>((s) => { },
"Kiba518"
,
() =>
{
//我是回調方法
});
//開啟異步有入參的方法,傳遞字符串參數Kiba518,之后返回int型結果518,
//並且在異步結束后,觸發回調方法,回調函數中可以獲得結果518
base
.AsyncLoad<
string
,
int
>((s) => {
return
518;
},
"Kiba518"
,
(result) =>
{
//我是回調方法 result是返回值518
});
}
}
|
看了上面的父子類后,是否感覺委托讓我們繁雜的線程世界變簡潔了呢?
----------------------------------------------------------------------------------------------------
第二核心應用——穿越你的世界:
接下來,我們來看委托的第二種核心用法,穿越的應用。
這個應用,是最常見,也最普通的應用了。因為委托是引用類型,所以A類里定義的委托,可以在被內存回收之前,被其他類調用。
我們經常會在各種論壇看到有人發問,A頁面如何調用B頁面的屬性、方法、父頁面獲取子頁面的屬性、方法,或者子頁面獲取父頁面的屬性、方法。
其實,只要定義好委托,並將委托正確的傳遞,就可以實現穿越的調用了。
下面我們看下穿越應用的代碼。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public
class
FirstDelegateSyntax
{
public
FirstDelegateSyntax()
{
Console.WriteLine(
" First 開始 "
);
SecondDelegateSyntax sds =
new
SecondDelegateSyntax(()=> {
Console.WriteLine(
" First傳給Second委托被觸發 "
);
});
sds.Excute();
Console.WriteLine(
" First 結束 "
);
}
}
public
class
SecondDelegateSyntax
{
public
Action Action {
get
;
set
; }
public
SecondDelegateSyntax(Action _action)
{
Console.WriteLine(
" Second的構造函數 "
);
Action = _action;
}
public
void
Excute()
{
Console.WriteLine(
" Second的Excute被觸發 "
);
Action();
}
}
|
我們可以看到,我們傳遞的委托,穿越了自身所屬的類。在SecondDelegateSyntax類中被觸發了。
運行結果如下:
第三核心應用——回調函數:
世界上本沒有回調函數,叫的人多了,也就有了。
請記住,所有的回調函數,都是委托的穿越應用,所有的回調函數;都是委托的穿越應用;所有的回調函數,都是委托的穿越應用。
重要的話要講三遍。
因為委托是引用類型,所以可以被[址傳遞]。函數是不可以被傳遞的。
當你傳遞函數的時候,其實是匿名傳遞了一個委托的地址。
結語
委托是我們最常用的語法,它將函數封裝成引用類型的變量,供其他單位調用。
因為委托的特質是引用類型,所以決定了委托是可以進行址傳遞。也就是說,委托是穿梭於我們系統代碼中的列車。
我們可以在列車上放很多很多東西,在需要的站點,叫停列車,並將托運的東西搬下來使用。
所以,理論上,只要我們利用好委托,就可以大量減少冗余的代碼。
但委托這種列車,是每個程序員都可以定義的,如果一個項目中有十個開發者,每個人都在定義委托,那么,就有可能出現定義了十個相同的委托的情況,這樣就出現了撞車的現象。
所以委托在使用的時候,盡量做到有序傳遞,即預先做好列車的行駛路線,讓委托按照路徑運行。盡量不要定義可以被任何單位調用的公共委托。
如果需要公共委托,可以采取反射的方式來調用。
模板方法(把委托當作參數傳遞到方法里去,通過傳進的委托參數,它“借用”制定的外部方法,來產生一個結果)
模板方法的好處是什么呢?一旦我們把代碼寫成了這樣只會,我們的GetTopNum方法呢,就不需要去動它了
我們只需要的是不斷擴張,我們之后想比較的成員變量就可以計算出,玩家每個成員中最高的玩家的名字(比如說我們之后增加了【金幣數】GetTopNum方法就不用動,只要新增一個小方法,如果用Lambda表達式的話,小方法也可以省去)我們不用去管我們計算的到底是哪個數據,我們只要把方法封裝在一個委托類型的變量中,作為參數來傳遞,傳遞到我們的模板方法中,我們的模板方法就一定可以計算出。某一個成員變量它的最高值
這里呢,就是初步介紹,如何通過[lambda表達式]當作一個實際參數,傳遞給方法的參數中