委托的定義
什么是委托?
委托實際上是一種類型,是一種引用類型。
微軟用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類中被觸發了。
運行結果如下:
第三核心應用——回調函數:
世界上本沒有回調函數,叫的人多了,也就有了。
請記住,所有的回調函數,都是委托的穿越應用,所有的回調函數;都是委托的穿越應用;所有的回調函數,都是委托的穿越應用。
重要的話要講三遍。
因為委托是引用類型,所以可以被[址傳遞]。函數是不可以被傳遞的。
當你傳遞函數的時候,其實是匿名傳遞了一個委托的地址。
結語
委托是我們最常用的語法,它將函數封裝成引用類型的變量,供其他單位調用。
因為委托的特質是引用類型,所以決定了委托是可以進行址傳遞。也就是說,委托是穿梭於我們系統代碼中的列車。
我們可以在列車上放很多很多東西,在需要的站點,叫停列車,並將托運的東西搬下來使用。
所以,理論上,只要我們利用好委托,就可以大量減少冗余的代碼。
但委托這種列車,是每個程序員都可以定義的,如果一個項目中有十個開發者,每個人都在定義委托,那么,就有可能出現定義了十個相同的委托的情況,這樣就出現了撞車的現象。
所以委托在使用的時候,盡量做到有序傳遞,即預先做好列車的行駛路線,讓委托按照路徑運行。盡量不要定義可以被任何單位調用的公共委托。
如果需要公共委托,可以采取反射的方式來調用。
后面我會繼續寫事件,消息,反射等語法,敬請期待。