1. 程式人生 > >淺談c#委託的四種用法及lambda匿名委託

淺談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類似。