C#快速入門教程(17)—— 委託、事件與Lambda表示式

曹化宇發表於2018-10-12

圖形化使用者介面(GUI)中,當我們單擊一個按鈕時就會有一個響應操作,這就是啟用了按鈕的單擊(click)事件(event);但是,在建立按鈕元件時並不能決定單擊按鈕後會有什麼操作,比如“確定”和“取消”就是完全不同的操作,此時,只能將按鈕單擊事件的響應程式碼交給使用按鈕的開發者來編寫。

在軟體開發中,還會許多類似的情形出現,比如,某個元件需要特定的操作,但在開發時無法確認具體的實現,或者使用者可以自定義操作;此時,就可以通過一種機制,讓元件的使用者來編寫實現程式碼。在C#中,可以使用委託(delegate)來處理這種情況,而事件則是委託的一種應用形式。

下面的示例,假設在軟體執行過程中需要做一些記錄,以便能夠更有效地分析軟體使用過程中的問題,但是,開發日誌記錄元件時並不確定使用什麼形式儲存資料,如使用檔案、資料庫等,此時,就可以使用委託來解決。

下面的程式碼,我們定義了一個名為DLogger的委託型別。

namespace ConsoleTest
{
    public delegate void DLogger(string msg);
}

程式碼中可以看到,除了delegate關鍵字,其他的內容與方法的定義非常相似,沒錯,因為很多情況下,委託就是通過方法完成具體的工作。

下面的程式碼,我們建立了CLog類,用於完成日誌的記錄工作。

public static class CLog
{
    public static DLogger Logger = new DLogger(DefaultLogger);
    // 預設的處理方法
    private static void DefaultLogger(string msg)
    {
        Console.WriteLine("預設操作");
        Console.WriteLine(msg);
    }
    // 日誌記錄方法
    public static void Log(string msg)
    {
        Logger(msg);
    }
    //
}

在一個專案中,日誌記錄操作的方式應該是一致的,所以,我們將CLog類定義為靜態類,其成員也都是靜態的,其中:

  • Logger物件定義為DLogger委託型別,並初始化為呼叫DefaultLogger()方法。
  • DefaultLogger()方法,用於預設的日誌記錄操作。
  • Log()方法,提供給使用者呼叫的日誌記錄方法。

下面的程式碼,我們試用一下CLog.Log()方法的預設執行效果。

static void Main(string[] args)
{
        CLog.Log("Hello Delegate");
}

程式碼執行結果如下圖所示。

enter image description here

那麼,我們如何修改CLog.Log()方法的操作呢?下面的程式碼就可以做到。

static void Main(string[] args)
{
        // 修改日誌委託的實現
        CLog.Logger = new DLogger(WriteLog);
        //
        CLog.Log("Hello Delegate");
}
//
static void WriteLog(string msg)
{
        Console.WriteLine("日誌寫入資料庫");
        Console.WriteLine(msg);
}

程式碼中,我們定義了一個WriteLog()方法,請注意它的返回值和引數設定應與DLogger委託型別保持一致;然後,在Main()方法中,將CLog.Logger重新設定為呼叫WriteLog()方法;執行結果如下圖所示。

enter image description here

示例中,大家可以看到,WriteLog()方法在整個軟體中只需要呼叫一次即可,此時單獨定義一個方法顯得有些多餘;在這種情況下,我們可以使用Lambda表示式來完成相同的任務,如下面的程式碼。

static void Main(string[] args)
{
        //
        CLog.Logger = (string msg) =>
        {
            Console.WriteLine("日誌寫入資料庫");
            Console.WriteLine(msg);
        };
        //
        CLog.Log("Hello Delegate");
}

這裡,使用Lambda表示式重新設定了CLog.Logger委託物件的操作,此處使用=>符號,這是Lambda表示式的標誌,而Lambda表示式定義的格式如下。

(<引數列表>) => {
   <語句塊>
};

我們可以看到,Lambda表示式的定義非常像一個簡化的方法定義,只是沒有了方法名,而且不需要指定返回值型別;實際上,Lambda表示式的是可以返回資料的,只需要在<語句塊>中使用return語句返回資料即可,就像方法的實現一樣。如下面的程式碼。

// 加法運算委託
delegate int DSum(int x, int y);
//
static void Main(string[] args)
{
        //
        DSum sum = (int num1, int num2) =>
        {
            return num1 + num2;
        };
        //
        int x = 10;
        int y = 99;
        Console.WriteLine("{0}+{1}={2}", x, y, sum(x, y));
}

本例中,我們定義了DSum委託型別,它包括兩個int型別的引數,其返回值同樣是int型別。Main()方法中,我們使用Lambda表示式定義了sum物件(DSum委託型別)的實現,其中將返回兩個引數相加的和。程式碼執行結果如下圖所示。

enter image description here

以上,我們看到了如何使用方法和Lambda表示式來實現委託物件的具體操作,在實際應用中,如果委託型別沒有返回值,我們還可以使用一個委託物件同時完成多個實現,如下面的程式碼。

static void Main(string[] args)
{
        CLog.Logger = new DLogger(WriteLog);
        CLog.Logger += new DLogger(SendLog);
        //
        CLog.Log("多路廣播委託");
}
//
static void WriteLog(string msg)
{
        Console.WriteLine("日誌寫入資料庫");
        Console.WriteLine(msg);
}
//
static void SendLog(string msg)
{
        Console.WriteLine("日誌傳送到遠端伺服器");
        Console.WriteLine(msg);
}

本例,我們定義了兩個方法,分別是WriteLog()和SendLog()方法,它們的引數和DLogger委託型別定義的相同的,並且沒有返回值。Main()方法中,我們通過=和+=運算子將兩個方法的實現都新增到了CLog.Logger委託物件中,當呼叫CLog.Log()方法時,就會呼叫WriteLog()和SendLog()兩個方法,執行結果如下圖所示。

enter image description here

如果大家設計軟體的圖形介面就會發現,控制元件(如按鈕)的事件的響應方法就是這樣進行關聯的,而這種操作就叫做多路廣播委託(multicast delegate),簡稱多播委託。

CHY軟體小屋原創作品!

相關文章