淺談c#委託的四種用法及lambda匿名委託
c#委託是一個類,可以定義一種方法型別,將有這種型別的函式當做引數進行傳遞,即他是一個可以把方法作為引數的類。
這裡通過一個小功能分別說明一下c#委託(delegate、Action、Func、predicate)的用法。
如下圖所示:
Form1窗體裡有兩個進度條,點選start按鈕,他們同時進行讀取,這裡就需要用到多執行緒。因為兩個進度條如果在一個執行緒內,只能一個一個的讀取。而用到多執行緒就要跨執行緒訪問控制元件,這裡最簡單方法就是呼叫允許跨執行緒訪問控制元件的方法,雖然一句話就可以解決,但是在複雜的程式中會造成一些莫名其妙的錯誤,所以用委託可以解決這個問題。
1. delegate
這裡先用委託使兩個進度條可以逐個讀取。然後在用跨執行緒委託的方式使他們可以同時進行讀取。
先定義一種委託型別並宣告委託物件:
public delegate void SetProgressBar(int value);
SetProgressBar setProgressBar;
通俗的講, setProgressBar是一個可以傳遞帶一個int型別引數函式的委託。那就可以定義兩個帶int型別引數的函式,來讓setProgressBar當引數傳遞即可。
private void setProgressValue1(int value)
{
progressBar1.Value = value ;
}
private void setProgressValue2(int value)
{
progressBar2.Value = value;
}
上面是分別將vlue的值賦給progressBar1和progressBar2的value屬性。
然後就可以將上面兩個函式當作引數傳遞給setProgressBar委託。那麼怎樣將這兩個函式傳遞給setProgressBar委託人呢,這裡需要將這兩個函式註冊繫結給setProgressBar委託即可。
private void btnStart_Click(object sender, EventArgs e)
{
set ProgressBar = new SetProgressBar(setProgressValue1); //繫結方法1
setProgressValueMethod(setProgressValue1);
setProgressBar += setProgressValue2; //繫結方法2
setProgressValueMethod(setProgressValue2);
}
上面將方法一註冊繫結給了setProgressBar 委託,並呼叫了setProgressValueMethod方法,然後將setProgressValue2方法註冊繫結給了setProgressBar ,再次呼叫setProgressValueMethod方法。那麼,既然已經將兩個函式繫結給了setProgressBar 委託,執行這兩個函式的方法就可以交給setProgressBar 了,setProgressValueMethod方法裡有setProgressBar 怎樣給這兩個函式辦事情的程式碼:
private void setProgressValueMethod(SetProgressBar setProgressBar)
{
for (int i = 0; i <= 100; i++)
{
Application.DoEvents(); //可以處理其他事件,比如拖動窗體等
Thread.Sleep(50);
setProgressBar(i); //當引數使用。
}
}
這裡在一個迴圈體力呼叫委託,委託裡的引數就是繫結函式的引數,執行 setProgressBar(i),相當於執行了繫結在該委託上的那兩個函式,他們的執行順序當然是誰先註冊就先執行誰。
當然,用一般的方法也可以讓這兩個進度條逐一讀取,但是如果是讓兩個進度條同時讀取,並且在拖動窗體的時候,進度條還可以繼續讀取呢。這裡就要用到跨執行緒訪問控制元件。
private void setProgressBarMethod(int value)
{
progressBar2.Value = value;
}
private void setProgressBarValue()
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(50);
progressBar2.Invoke(setProgressBar, i); //委託給該控制元件
}
}
private void btnStart_Click(object sender, EventArgs e)
{
setProgressBar = new SetProgressBar(setProgressBarMethod);
Thread SetProgress = new Thread(setProgressBarValue); //執行緒呼叫該方法
SetProgress.Start();
for (int i = 0; i <= 100; i++)
{
Application.DoEvents();
Thread.Sleep(50);
progressBar1.Value = i;
}
}
這裡讓progressBar1在主執行緒上執行,讓progressBar2在SetProgress 執行緒上執行,就可以達到並行的目的。那麼在SetProgress執行緒裡我們需要改變progressBar2.value的值,而progressBar2屬於主執行緒的控制元件,那麼就需要跨執行緒訪問,這裡就要用到委託。即progressBar2.Invoke(setProgressBar, i);這句就是跨執行緒呼叫setProgressBar委託,第二個引數就是註冊在委託上的函式的引數。
這樣就可以實現連個進度條同時讀取了,由於第一個進度條在主執行緒中,當我們拖動窗體時,該進度條會停止讀取。這就是執行緒阻塞。
那麼這樣呼叫委託,跨執行緒訪問控制元件,回撥是不是很麻煩,我們可以使用其他三種委託方法以及lambda表示式使程式更加簡潔:
2. Action
Action是一個無返回值的泛型委託,下面是在該小功能下的使用方式:
private void btnStart_Click(object sender, EventArgs e)
{
int i=0;
Thread SetProgress = new Thread(() => {
progressBar2.Invoke(new Action<int>((n)=>{
for ( ; n <= 100; n++)
{
Application.DoEvents();
Thread.Sleep(50);
progressBar1.Value = n;
}}),i);
});
SetProgress.Start();
for (int m = 0; m <= 100; m++)
{
Application.DoEvents();
Thread.Sleep(50);
progressBar1.Value = m;
}
}
以上程式結合Action委託和lambda表示式使程式變的很簡潔。Action的引數是一個委託,前面的表示接受一個int引數的委託。這裡的委託直接用lambda表示式代替了,即它是一個匿名委託。
3. Func
Func是有返回值的泛型委託,它的最後一個引數是返回類。例如:Func <int>表示無參,返回值為型別的委託,
Func<object,string,int> 表示傳入引數為object、string, 返回值為int的委託
Func必須有返回值,不可void,最多16個引數
Func的使用方式與Action類似。
4. predicate
predicate 是返回bool型的泛型委託
predicate<int> 表示傳入引數為int 返回bool的委託
Predicate有且只有一個引數,返回值固定為bool,使用方法與Action類似。