徹底弄懂C#中delegate、event、EventHandler、Action、Func的使用和區別

高階夢想家發表於2023-04-03

【目錄】

1 委託

2 事件-概念的引出

3 事件-關於異常

4 事件-關於非同步

5 委託-Func與Action

 

1 委託

在.NET中定義“委託”需要用到delegate關鍵字,它是存有對某個方法的引用的一種引用型別變數,類似於 C 或 C++ 中函式的指標。“委託”主要有兩大作用:

(1)將方法當作引數傳遞

(2)方法的一種多型(類似於一個方法模板,可以匹配很多個方法)

下面,給出一個展現了上述兩大作用的委託程式碼示例:

        //定義一個委託
        public delegate int MyDelegate(int x, int y);

        //與委託匹配的一個方法
        public static int Add(int a, int b)
        {
            return a + b;
        }

        //與委託匹配的另一個方法
        public static int Reduce(int a, int b)
        {
            return a - b;
        }

        //示例:將委託/方法當引數傳遞
        public static int Test(MyDelegate MD)
        {
            return MD(10, 20);
        }

        static void Main(string[] args)
        {
            int a, b, x, y;

            MyDelegate md;

            //將委託指向Add這個方法,並進行相關操作
            md = Add;
            a = md(1, 2);
            b = Test(md);

            //再將委託指向Reduce這個方法,並進行相關操作
            md = Reduce;
            x = md(7, 2);
            y = Test(md);

            Console.WriteLine($"1+2={a},10+20={b},7-2={x},10-20={y}");
            Console.ReadLine();
        }

執行以上程式,輸出結果如下:

1+2=3,10+20=30,7-2=5,10-20=-10

委託也可以使用+=/-=來實現“釋出/訂閱”模式,示例程式碼如下:

        //定義一個委託
        public delegate void MyDelegate1(int x);

        public static void Method1(int a)
        {
            Console.WriteLine($"a={a}");
        }

        public static void Method2(int b)
        {
            Console.WriteLine($"b={b}");
        }

        static void Main(string[] args)
        {
            MyDelegate1 md = null;
            md += Method1;
            md += Method2;
            md(35);

            Console.ReadLine();
        }

以上程式輸出如下:

a=35

b=35

但是委託有一個弊端,它可以使用“=”將所有已經訂閱的取消,只保留=後的這一個訂閱。

為瞭解決這個弊端,事件event應運而生。

 

2 事件-概念的引出

事件event是一種特殊的委託,它只能+=,-=,不能直接用=。

event在定義類中(釋出者)是可以直接=的,但是在其他類中(訂閱者)就只能+= -=了,也就是說釋出者釋出一個事件後,訂閱者針對他只能進行自身的訂閱和取消。

下面是定義一個事件的程式碼:

        //定義一個委託
        public delegate void MyDelegate1(int x);
        //定義一個事件
        public event MyDelegate1 emd;

經過長久的經驗積累後,人們發現,絕大多數事件的定義,是用public delegate void XXX(object sender, EventArgs e);這樣一個委託原型進行定義的,是一件重複性的工作,於是,EventHandler應運而生。它的出現就是為了避免這種重複性工作,並建議儘量使用該型別作為事件的原型。

//@sender: 引發事件的物件
//@e: 傳遞的引數
public delegate void EventHandler(object sender, EventArgs e);

//使用
public event EventHandler emd;


下面給出一個使用事件的具體示例:

        public class Demo
        {
            public event EventHandler emd;
            public void RaiseEvent()
            {
                emd(this, EventArgs.Empty);
            }
        }

        static void Main(string[] args)
        {
            var instance = new Demo();
            instance.emd += (sender, arg) =>
            {
                Console.WriteLine("執行事件1!");
            };

            instance.emd += (sender, arg) =>
            {
                Console.WriteLine("執行事件2!");
            };

            instance.RaiseEvent();

            Console.ReadLine();
        }

這裡我們先定義一個Demo類,其內部有個事件是emd,我們給他開放了一個介面RaiseEvent,如果誰敢呼叫它,那麼,它就觸發報警事件emd。

這裡模擬了2個訂閱者,分別處理報警事件emd。

程式執行結果如下:

執行事件1!

執行事件2!

同時,我們也可以看出:事件是按照+=的訂閱先後順序執行的。

3 事件-關於異常

現在,我們在第一個訂閱者中加入異常,如下:

    instance.emd += (sender, arg) =>
    {
        Console.WriteLine("執行事件1!");
        throw new Exception("執行事件1,錯誤");
    };

 

執行後發現,第1個訂閱者事件觸發丟擲異常後,第2個訂閱者的事件沒有執行。

可見,如果你想讓所有訂閱者都執行處理的話,那每個訂閱者必須在訂閱程式內自己處理好異常,不能丟擲來!

 

4 事件-關於非同步

如果事件的訂閱者中有一個是“非同步”處理,又會是什麼情況?

下面我們把第1個訂閱者改為非同步處理,程式碼如下:

    instance.emd += async (sender, arg) =>
    {
        Console.WriteLine("執行事件1!");
        await Task.Delay(1000);
        Console.WriteLine("執行事件1!完畢");
    };

執行後輸出如下:
執行事件1!

執行事件2!

執行事件1!完畢

可見,非同步的事件處理沒有阻塞程式,很好的起到了非同步方法的作用。

 

5 委託-Func與Action

本文最開始探討委託,然後直接順到了事件的相關話題上。其實,關於委託還有一個重點話題漏掉了,那就是Func與Action。

在委託delegate出現了很久以後,微軟的.NET設計者們終於領悟到,其實所有的委託定義都可以歸納並簡化成只用Func與Action這兩個語法糖來表示。其中,Func代表有返回值的委託,Action代表無返回值的委託。有了它們兩,我們以後就不再需要用關鍵字delegate來定義委託了。

同時,若再用lambda表示式取代被委託指向的具體方法,則整個委託的“定義+賦值”兩步將大大簡化(lambda表示式本來也是方法定義的一種簡化形式)。

下面,把最開始委託章節中關於加減法的程式程式碼,用Func與lambda表示式進行簡化改造,改造後的程式碼如下:

        //示例:將委託/方法當引數傳遞
        public static int Test(Func<int, int, int> MD)
        {
            return MD(10, 20);
        }

        static void Main(string[] args)
        {
            int a, b, x, y;

            Func<int, int, int> md;

            //將委託指向加法,並進行相關操作
            md = (t, v) => t + v;
            a = md(1, 2);
            b = Test(md);

            //再將委託指向減法,並進行相關操作
            md = (t, v) => t - v;
            x = md(7, 2);
            y = Test(md);

            Console.WriteLine($"1+2={a},10+20={b},7-2={x},10-20={y}");
            Console.ReadLine();
        }

是不是程式碼大大簡化了?簡化了哪些內容,你可以前後對比一下...(本文完)

 

相關文章