1. 程式人生 > 其它 >C# 事件與委託

C# 事件與委託

技術標籤:c#事件驅動event委託事件與委託

1. 最近重溫c# 基礎, 看到微軟MVP劉鐵錳老師的視訊,其中委託和事件,講的讓我受益匪淺,所以記錄下學習心得。

2. 委託(delegate):

2.1 在c# 中,委託是一種類(class), 由於普通類(class)是一種資料型別,所以委託(delegate) 也是一種資料型別,它與類同級。

只是,由於它比較特殊,宣告的方式與一般類不同,根據劉鐵錳老師視訊,這樣子宣告方式主要是為了照顧可讀性和c/c++ 傳統。

2.2 宣告委託的位置 (避免宣告巢狀型別)

在我以前,使用Wimform 程式設計時,一般在多執行緒訪問UI執行緒時,主要使用委託,但是由於對委託理解不夠深入(delegate與class 同級),一般定義委託的位置都是類裡面。形成巢狀型別。

根據教程中提到,委託也是一種類,所以,宣告委託時,儘量與類的宣告同級。

2.3 在c#中的委託Action 和 Func<T> 泛型。

2.4 委託兩種呼叫方式, 如:

public delegate void Action(int x, int y);

Action action = new Action();

2.4.1 同步呼叫:action.Invoke(1, 2); 或者 action(1, 2);

2.4.2 非同步呼叫: action.BeginInvoke(1, 2);

3. 事件

3.1. 初步瞭解事件:

(1)定義: 單詞Event , 中文譯為“事件”,能夠發生的事情。

(2)角色: 是使用的物件,或者類,具備通知能力成員。 棣屬於類或物件,不能脫離類或物件。

(中文翻譯) 事件(Event) 是一種能使物件或者類能夠提供通知的成員

(英文原版) An event is a member that enables an object or class to provide notifications.

(3) 使用: 使用者物件或者類間的動作協調與資訊傳遞 (即訊息推送)。

原理: 事件模型 (Event model)

其中, a. 事件訂閱者,事件訊息的接受者, 事件的響應者,事件的處理者,被事件所通知的物件, 這5個都是指同一個東西。

b. 事件訊息, 事件資訊,事件資料,事件引數, 這4個都是指同一個東西。

3.2 事件的應用

(1) 事件模型的5個組成部分:

a. 事件擁有者(event source, 類或物件)

b. 事件成員( event, 成員)

c. 事件響應者(event subscriber, 物件)

d. 事件處理器(event handler, 成員) ----- 本質上是一個回撥的成員方法

e. 事件訂閱 --- 把事件處理器 與事件關聯一起,本質是一種以委託為基礎的“約定”。

注意:

a1. 事件處理器是方法成員,

a2. 掛接事件處理器的時候,可以使用委託例項(委託本質上一種類) ,也可以直接使用方法名,這是個語法糖。

a3. 事件處理器對事件的訂閱不是隨意的,編譯器會匹配與是否由宣告事件時所使用的委託型別(再次說明委託是一個類)來檢測。

a4. 事件可以同步呼叫(Event.Invoke),或者非同步呼叫 (Event.BeginInvoke).

有這幾種 情況,

4. 自定義事件

4.1 定義事件資訊型別,按照.Net 規定,事件資訊的命名,以事件 + EventArgs 字尾,並繼承EventArgs

public class OrderEventArgs:EventArgs
{
    public String DishName { get; set; }

    public String Size { get; set; }
}

4.2定義事件擁有者和委託:

根據.Net 的規定,如果委託,是為了宣告的某個事件而準備的,需要在委託後面增加EventHandler 字尾。

     /// <summary>
    /// 使用EventHandler 用意:
    /// 1. 看到EventHandler 字尾, 這個委託是用來宣告事件
    /// 2. 表明這個委託是用來宣告事件,約束事件處理器
    /// 3. 委託示例,儲存事件處理器
    /// </summary>
    /// <param name="customer"></param>
    /// <param name="args"></param>
    public delegate void OrderEventHandler(Customer customer, OrderEventArgs args);

     /// <summary>
    /// 事件擁有者
    /// </summary>
    public class Customer
    {
    }

/// 使用EventHandler 用意:
/// 1. 看到EventHandler 字尾, 這個委託是用來宣告事件
/// 2. 表明這個委託是用來宣告事件,約束事件處理器
/// 3. 委託示例,儲存事件處理器

委託也是一種類,所以,宣告委託時,儘量與類的宣告同級,避免巢狀。

4.3 定義事件

由於在上面提到,事件是使用的物件,或者類,具備通知能力成員。 棣屬於類或物件,不能脫離類或物件。

因此,需要把事件定義到事件擁有者上面,並且使用上面定義的委託宣告來約束事件。

/// <summary>
    /// 使用EventHandler 用意:
    /// 1. 看到EventHandler 字尾, 這個委託是用來宣告事件
    /// 2. 表明這個委託是用來宣告事件,約束事件處理器
    /// 3. 委託示例,儲存事件處理器
    /// </summary>
    /// <param name="customer"></param>
    /// <param name="args"></param>
    public delegate void OrderEventHandler(Customer customer, OrderEventArgs args);

    /// <summary>
    /// 事件擁有者
    /// </summary>
    public class Customer
    {
        //建立儲存事件處理的委託
        private OrderEventHandler orderEventHandler;

        //建立委託約束的事件  -- 事件最基本的建立放方式, 
        public event OrderEventHandler Order
        {
            add        //新增器
            {
                this.orderEventHandler += value;
            }

            remove    //移除器
            {
                this.orderEventHandler -= value;
            }
        }
    }

4.4 定義事件響應者和事件處理器

 /// <summary>
    /// 事件響應者
    /// </summary>
    public class Waiter
    {
        /// <summary>
        /// 事件處理器
        /// </summary>
        /// <param name="customer"></param>
        /// <param name="args"></param>
        public void EventAction(Customer customer, OrderEventArgs e)
        {
            Console.WriteLine("I will serve you the dish - {0}", e.DishName);

            double price = 10;

            switch (e.Size)
            {
                case "small":
                    customer.Bill = price * 0.5;
                    break;

                case "large":
                    customer.Bill = price * 1.5;
                    break;
            }
        }   
    }

上面也提到,事件處理器(event handler, 成員) ----- 本質上是一個回撥的成員方法

4.5 在事件擁有者中,增加觸發方法

 /// <summary>
    /// 使用EventHandler 用意:
    /// 1. 看到EventHandler 字尾, 這個委託是用來宣告事件
    /// 2. 表明這個委託是用來宣告事件,約束事件處理器
    /// 3. 委託示例,儲存事件處理器
    /// </summary>
    /// <param name="customer"></param>
    /// <param name="args"></param>
    public delegate void OrderEventHandler(Customer customer, OrderEventArgs args);

    /// <summary>
    /// 事件擁有者
    /// </summary>
    public class Customer
    {
        //建立儲存事件處理的委託,但是沒有分配
        private OrderEventHandler orderEventHandler;

        //建立委託約束的事件  -- 事件最基本的建立放方式, 
        public event OrderEventHandler Order
        {
            add
            {
                this.orderEventHandler += value;
            }

            remove
            {
                this.orderEventHandler -= value;
            }
        }

        public double Bill { get; set; }

        public void PlayTheBill()
        {
            Console.WriteLine("I will pay ¥{0}", this.Bill);
        }

        public void Walkln()
        {
            Console.WriteLine("Walk into the restaurant");
        }

        public void SitDown()
        {
            Console.WriteLine("Sit down");
        }

        //事件觸發,都是由事件擁有者,內部一些邏輯方法觸發, 然後呼叫事件處理器
        public void Think()
        {
            for (int ik = 0; ik < 5; ik++)
            {
                Console.WriteLine("Let me think 。。。。。");
                Thread.Sleep(1000);
            }

            //觸發事件處理器,需要判斷是否增註冊了事件處理
            if (this.orderEventHandler != null)
            {
                OrderEventArgs e = new OrderEventArgs();

                e.DishName = "KongPao Chicken";
                e.Size = "large";

                this.orderEventHandler.Invoke(this, e);

                //函式指標用法
                //this.orderEventHandler(this, e);
            }
        }

        public void Action()
        {
            Console.WriteLine("Please input enter: ");
            Console.ReadLine();

            this.Walkln();
            this.SitDown();
            this.Think();
        }

    }

5. 關於事件宣告

5.1 事件的宣告:

a. 完整宣告, 上面就是完整宣告,

(1) 在事件擁有者類中先定義委託,
private OrderEventHandler orderEventHandler;

(2)使用委託約束定義事件

//建立委託約束的事件 -- 事件最基本的建立放方式,
public event OrderEventHandler Order
{
add
{
this.orderEventHandler += value;
}

remove
{
this.orderEventHandler -= value;
}
}

b. 簡略宣告(欄位式宣告,field-like)

public eventOrderEventHandler Order;

//訪問許可權, event 建立事件, OrderEventHandler (哪種委託型別,作為事件的約束),Order 事件名字

5.2 有了委託欄位/屬性, 為什麼還要事件?

為了程式的邏輯更加有道理,更加安全。

所以,事件的本質,是委託欄位的一個包裝器。

a. 這個包裝器對委託欄位訪問器限制作用。

b. 封裝的一個重要功能就是隱藏。

c. 事件對外界隱藏了委託例項的大部分功能,僅暴露了增加/移除事件處理器功能。

5.3 根據.Net 規定,最終修改如下:

 /// <summary>
    /// 使用EventHandler 用意:
    /// 1. 看到EventHandler 字尾, 這個委託是用來宣告事件
    /// 2. 表明這個委託是用來宣告事件,約束事件處理器
    /// 3. 委託示例,儲存事件處理器
    /// </summary>
    /// <param name="customer"></param>
    /// <param name="args"></param>
    //public delegate void OrderEventHandler(Customer customer, OrderEventArgs args);
    public delegate void OrderEventHandler(Object sender, EventArgs e);
    
    /// <summary>
    /// 事件擁有者
    /// </summary>
    public class Customer
    {
        /*
        //建立儲存事件處理的委託
        private OrderEventHandler orderEventHandler;

        //建立委託約束的事件  -- 事件最基本的建立放方式, 
        public event OrderEventHandler Order
        {
            add
            {
                this.orderEventHandler += value;
            }

            remove
            {
                this.orderEventHandler -= value;
            }
        }*/


        //使用指定的委託型別,定義事件名字
        public event OrderEventHandler Order;

        public double Bill { get; set; }

        public void PlayTheBill()
        {
            Console.WriteLine("I will pay ¥{0}", this.Bill);
        }

        public void Walkln()
        {
            Console.WriteLine("Walk into the restaurant");
        }

        public void SitDown()
        {
            Console.WriteLine("Sit down");
        }

        //事件觸發,都是由事件擁有者,內部一些邏輯方法觸發, 然後呼叫事件處理器
        public void Think()
        {
            for (int ik = 0; ik < 5; ik++)
            {
                Console.WriteLine("Let me think 。。。。。");
                Thread.Sleep(1000);
            }

            //觸發事件處理器
            //if (this.orderEventHandler != null)
            if(this.Order != null)
            {
                OrderEventArgs e = new OrderEventArgs();

                e.DishName = "KongPao Chicken";
                e.Size = "large";

                //this.orderEventHandler.Invoke(this, e);

                //函式指標用法
                //this.orderEventHandler(this, e);

                //直接呼叫
                this.Order.Invoke(this, e);
            }
        }

        public void Action()
        {
            Console.WriteLine("Please input enter: ");
            Console.ReadLine();

            this.Walkln();
            this.SitDown();
            this.Think();
        }

    }
    /// <summary>
    /// 事件響應者
    /// </summary>
    public class Waiter
    {
        /// <summary>
        /// 事件處理器
        /// </summary>
        /// <param name="customer"></param>
        /// <param name="args"></param>
        //public void EventAction(Customer customer, OrderEventArgs e)
        public void EventAction(Object sender, EventArgs e)
        {
            Customer customer = sender as Customer;
            OrderEventArgs e1 = e as OrderEventArgs;

            Console.WriteLine("I will serve you the dish - {0}", e1.DishName);

            double price = 10;

            switch (e1.Size)
            {
                case "small":
                    customer.Bill = price * 0.5;
                    break;

                case "large":
                    customer.Bill = price * 1.5;
                    break;
            }
        }   
    }

執行事件

  static void Main(string[] args)
        {
            //建立事件擁有者
            Customer customer = new Customer();

            //建立事件處理者
            Waiter waiter = new Waiter();

            //註冊事件處理器
            customer.Order += waiter.EventAction;

            //觸發事件
            customer.Action();

            customer.PlayTheBill();
        }

綜上,委託與事件原來是這層關係。通過劉鐵錳老師講解,有終於理解了,事件是什麼,委託是什麼。

參考《劉鐵錳老師C# 基礎視訊講義》