1. 程式人生 > >C#委託(delegate、Action、Func、predicate)和事件

C#委託(delegate、Action、Func、predicate)和事件

一、前言

剛開始工作的時候,覺得委託和事件有些神祕,而當你理解他們之後,也覺得好像沒有想象中的那麼難。在專案中運用委託和事件,你會發現他非常棒,這篇博文算是自己對委託和事件的一次梳理和總結。

二、委託

C#中的委託,相當於C++中的指標函式,但委託是面向物件的,是安全的,是一個特殊的類,當然他也是引用型別,委託傳遞的是對方法的引用。

2.1、delegate

宣告委託就必須使用關鍵字“delegate”,委託是先宣告,後例項化。至少0個引數,至多32個引數

格式如下所示:

private delegate string GetAsString();

委託是一個類,所以他的例項化跟類的例項化一樣,只是他總是接受一個將委託方法作為引數的建構函式。呼叫委託方法就有兩種方式,如下所示:

int i = 10;
var method = new GetAsString(i.ToString);
//呼叫方法一
Console.WriteLine($"method方法{method()}");
//呼叫方法二
Console.WriteLine($"method.Invoke方法{method.Invoke()}");

執行結果:

2.2、Action

Action是無返回值的泛型委託,可以接受0個至16個傳入引數

Action 表示無參,無返回值的委託

Action<int,string> 表示有傳入引數int,string無返回值的委託

前面我們【Log4Net 日誌記錄的實現】中,就使用了Action。如:

 public static void Debug(string message, Action RegistedProperties)
        {
            RegistedProperties();
            log.Debug(message);
        }

呼叫方式為:

PFTLog.Debug("測試擴充套件欄位", () => {
    LogicalThreadContext.Properties["LogType"] = "擴充套件欄位內容";
});

在執行中,直接執行Action中的內容即可。

2.3、Func

Func是有返回值的泛型委託,可以接受0個至16個傳入引數

Func<int> 表示無參,返回值為int的委託

Func<object,string,int> 表示傳入引數為object, string 返回值為int的委託

public static decimal GetTotal(Func<int, int, decimal> func, int a, int b)
{
    return func(a, b);
}

呼叫方式

var total = GetTotal((a, b) => { return (decimal)a + b; }, 1, 2);
Console.WriteLine($"結果為{total}");

執行結果

2.4、predicate

predicate 是返回bool型的泛型委託,只能接受一個傳入引數

predicate<int> 表示傳入引數為int 返回bool的委託

定義一個方法:

public static bool FindPoints(int a)
{
    return a >= 60;
}

定義Predicate委託

 Predicate<int> predicate = FindPoints;

呼叫

var points = new int[] {
    10,
    50,
    60,
    80,
    100 };
var result = Array.FindAll(points, predicate);

Console.WriteLine($"結果為{string.Join(";", result)}");

執行結果

2.5、多播委託

前面的只包含了一個方法的呼叫,委託可以包含多個方法,這種委託就叫做多播委託。多播委託利用“+=”和“-+”兩種運算子進行新增和刪除委託。

先定義兩個方法

public static void MultiplyByTwo(double v)
{
    double result = v * 2;
    Console.WriteLine($"傳值:{v};MultiplyByTwo結果為{result}");
}
public static void Square(double v)
{
    double result = v * v;
    Console.WriteLine($"傳值:{v};Square結果為{result}");
}

然後呼叫

Action<double> operations = MultiplyByTwo;
operations(1);
operations += Square;
operations(2);

執行結果:

三、事件

事件是基於委託,為委託提供一種釋出/訂閱機制,宣告事件需要使用event關鍵字。

釋出者(Publisher):一個事件的發行者,也稱作是傳送者(sender),其實就是個物件,這個物件會自行維護本身的狀態資訊,當本身狀態資訊變動時,便觸發一個事件,並通知說有的事件訂閱者;

訂閱者(Subscriber):對事件感興趣的物件,也稱為Receiver,可以註冊感興趣的事件,在事件發行者觸發一個事件後,會自動執行這段程式碼

是不是看到sender,就有種很熟悉的感覺!!!先不忙著急,我們先看下事件的宣告和使用

有這樣一個應用場景,如果系統有異常,需要及時的通知管理員。那麼需要在我們的日誌記錄裡面新增通知管理員的功能,但是問題來了,該怎麼通知管理員呢?至少現在無法知道。所以我們就需要在使用到事件。

新增程式碼如下,如果不知道日誌功能的可以參考【Log4Net 日誌記錄的實現】

//宣告一個通知的委託
public delegate void NoticeEventHander(string message);
//在委託的機制下我們建立以個通知事件
public static event NoticeEventHander OnNotice;

呼叫方式

public static void Debug(string message, Action RegistedProperties)
{
    RegistedProperties();
    log.Debug(message);
    //執行通知
    OnNotice?.Invoke($"系統異常,請及時處理,異常資訊:{message}");
}

在引用場景的程式碼,先定義一個通知管理員的方法(這裡我們直接Console.WriteLine出來)

public static void Notice(string message)
{
    Console.WriteLine($"通知內容為{message}");
}

先註冊,然後觸發異常訊息

//註冊方式一
PFTLog.OnNotice += Notice;
//註冊方式二
//PFTLog.OnNotice += new PFTLog.NoticeEventHander(Notice);

PFTLog.Debug("測試擴充套件欄位", () => {
    LogicalThreadContext.Properties["LogType"] = "擴充套件欄位內容";
});

執行結果

這裡面我只需要定義好釋出者,你可以以任何方式訂閱,是不是很非常簡單。

弄明白了上面的事件,我們在來說說.Net經常出現的object sender和EventArgs e

.Net Framework的編碼規範:

一、委託型別的名稱都應該以EventHandler結束

二、委託的原型定義:有一個void返回值,並接受兩個輸入引數:一個Object 型別,一個 EventArgs型別(或繼承自EventArgs)

三、事件的命名為 委託去掉 EventHandler之後剩餘的部分

四、繼承自EventArgs的型別應該以EventArgs結尾

現在我們以一個新書釋出的自定義事件為例

建立對應的類檔案:

事件者釋出程式碼:

public class BookInfoEventArgs : EventArgs
{
    public BookInfoEventArgs(string bookName)
    {
        BookName = bookName;
    }

    public string BookName { get; set; }

}
public class BookDealer
{
    //泛型委託,定義了兩個引數,一個是object sender,第二個是泛型 TEventArgs 的e
    //簡化了如下的定義
    //public delegate void NewBookInfoEventHandler(object sender, BookInfoEventArgs e);
    //public event NewBookInfoEventHandler NewBookInfo;
    public event EventHandler<BookInfoEventArgs> NewBookInfo;
    public void NewBook(string bookName)
    {
        RaiseNewBookInfo(bookName);
    }

    public void RaiseNewBookInfo(string bookName)
    {
        NewBookInfo?.Invoke(this, new BookInfoEventArgs(bookName));
    }
}

事件訂閱者

public class Consumer
{
    public Consumer(string name)
    {
        Name = name;
    }

    public string Name { get; set; }

    public void NewBookHere(object sender, BookInfoEventArgs e)
    {
        Console.WriteLine($"使用者:{Name},收到書名為:{ e.BookName}");
    }
}

事件訂閱和取消訂閱

var dealer = new BookDealer();
var consumer1 = new Consumer("使用者A");
dealer.NewBookInfo += consumer1.NewBookHere;
dealer.NewBook("book112");


var consumer2 = new Consumer("使用者B");
dealer.NewBookInfo += consumer2.NewBookHere;


dealer.NewBook("book_abc");

dealer.NewBookInfo -= consumer1.NewBookHere;


dealer.NewBook("book_all");

執行結果

經過這個例子,我們可以知道Object sender引數代表的是事件釋出者本身,而EventArgs e 也就是監視物件了。深入理解之後,是不是覺得也沒有想象中的那麼難了。

四、總結

這裡我們講了委託和事件,在.Net開發中使用委託和事件,可以減少依賴性和層的耦合,開發出具有更高的重用性的組