C#事件的原理和用法整理


事件是什么?

事件是能夠讓對象或類具備通知能力的成員。是一種類型成員(沒有產品就沒有發布,沒有公司就沒有上市)。它是用於對象和類之間的動作協調和信息傳遞的

 

事件模型如下:

“發生->響應”

5個動作——

  1. 我有一個事件
  2. 我關心這個事件
  3. 這個事件發生了
  4. 關心這個事件的人被通知到
  5. 被通知到的人根據拿到的事件信息對事件進行響應

 

5個部分——

  1. 事件的擁有者(event source 對象)
  2. 事件成員(event 成員) 事件是被動觸發的,它需要通過事件擁有者進行觸發
  3. 事件的響應者(eventsubscriber 對象)當事件發生時,哪些對象去響應
  4. 事件處理器(event handler 成員)響應者的方法成員
  5. 事件訂閱     把事件處理器和事件關聯在一起,本質上是一種以委托類型為基礎的約定

 

從響應者角度來看,他們除了收到事件通知,還接收到經由事件發送過來的與事件本身相關的信息,稱為“事件參數”(Event Args)

被通知的人根據事件參數對事件進行響應(處理事件),(處理事件)所做的事情,稱為事件處理器(Event Handler)

 

因此我們可以說 事件的功能就是=通知+可選的事件模型

我們可以通過一個簡單的代碼例子來理解5個組成部分的關系。

查看代碼
    class Program
    {
        static void Main(string[] args)
        {
            Timer timer = new Timer();//timer是事件擁有者
            timer.Interval = 1000;
            Boy boy = new Boy();//boy是事件響應者
            timer.Elapsed += boy.Action; //Elapsed是事件 +=是訂閱
            timer.Start();
            Console.ReadKey();
        }
    }

    public class Boy
    {
        //事件處理器
        internal void Action(object sender, ElapsedEventArgs e)
        {
            Console.WriteLine("hello");
        }
    }

我們用Boy作為事件響應者 用其事件處理器Action來訂閱事件擁有者Timer類對象中自帶的Elapsed事件

 

此外對於事件我們的使用情況通常有三種:

1、事件擁有者和響應者是兩個相互獨立的類,響應者類的處理器訂閱擁有者類的事件

在這里插入圖片描述

查看代碼

namespace EventExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Form form = new Form();
            Controller controller = new Controller(form);
            form.ShowDialog();
        }
    }

    class Controller
    {
        private Form form;
        public Controller(Form form)
        {
            if (form != null)
            {
                this.form = form;
                this.form.Click += this.Clicked;
            }
        }

        private void Clicked(object sender, EventArgs e)
        {
            this.form.Text = DateTime.Now.ToString();
        }
    }
}

2、事件擁有者和響應者屬於同一個類,這個類自己訂閱自己的事件

在這里插入圖片描述

namespace EventExample
{
    class Program
    {
        static void Main(string[] args)
        {
            MyForm myForm = new MyForm();
            myForm.Click += myForm.FormClicked;
            myForm.ShowDialog();
        }
    }

    class MyForm : Form
    {
        internal void FormClicked(object sender, EventArgs e)
        {
            this.Text = DateTime.Now.ToString();
        }
    }
}

3、事件擁有者作為響應者的一個成員,或者是事件響應者作為擁有者的一個成員

在這里插入圖片描述

查看代碼

namespace EventExample
{
    class Program
    {
        static void Main(string[] args)
        {
            MyForm myForm = new MyForm();
            myForm.ShowDialog();
        }
    }

    class MyForm : Form
    {
        private TextBox TextBox;
        private Button button;
        public MyForm()
        {
            this.TextBox = new TextBox();
            this.button = new Button();
            this.Controls.Add(this.button);
            this.Controls.Add(this.TextBox);
            this.button.Click += this.ButtonClicked;
            this.button.Text = "Click";
            this.button.Top = 50;
        }

        private void ButtonClicked(object sender, EventArgs e)
        {
            this.TextBox.Text = "Hello World!!!!!!!!!!!!";
        }
    }
}

 

上面我們用的是C#幫我們寫好的事件

那我們如何來自定義一個事件呢?C#自帶事件的內部實現是什么樣的呢?

 

還記得我們之前說的,事件是基於委托的嗎。這個特性會在我們聲明事件時進行一個體現

 

委托有以下作用:

類型兼容:委托對事件做了一個約束,規定了事件和事件處理器應當匹配

存儲方法的引用:委托類型的實例將這組匹配信息保存了下來

 

因此我們說事件需要搭配一個委托。它通常是聲明在事件擁有者類外的

// 步驟1,聲明delegate  如果這個委托是為了約束某個事件而聲明的委托 那么通常就將其命名為“事件名+EventHandler”
    public delegate void MyEventHandler(object sender, System.EventArgs e);

我們可以給個更加具體的例子,比如我們聲明一個點餐的委托,我們想讓Customer顧客作為事件擁有者,還要傳遞價格、大小等事件參數。那我們就可以這樣聲明

public delegate void OrderEventHandler(Customer customer, EventArgs e);

 

之后我們就可以聲明我們的事件了。(記住事件是屬於事件擁有者的,它需要寫在事件擁有者類的內部)

有完整聲明和簡略聲明兩種形式

完整聲明:

private OrderEventHandler OrderEventHandler;//聲明委托類型字段。用於存儲和引用事件處理器

public event OrderEventHandler Order//聲明事件Order。用OrderEventHandler來約束事件
{
    add//事件處理器的添加器
    {
        this.OrderEventHandler += value;
    }

    remove//事件處理器的移除器
    {
        this.OrderEventHandler -= value;
    }
}

簡略聲明:

private event MyEventHandler myevent;

 

多種事件訂閱格式

this.button3.Click += MyButton_Click;//第一種掛接事件方法
this.button3.Click += new EventHandler(this.MyButton_Click);//第二種
this.button3.Click += delegate (object sender, EventArgs e)//第三種 匿名方法
{
    this.MyTextBox.Text = "haha";
};
this.button3.Click += (sender,e) =>  //第四種 lambda表達式
{
    this.MyTextBox.Text = "hoho";
};

事件與委托的關系
事件真的是“以特殊方式聲明的委托字段/實例嗎”?
不是!只是聲明的時候“看起來像”(對比委托字段的和事件的簡化聲明,field-like)


事件聲明的時候使用了委托類型,簡化聲明造成事件看上去像一個委托的字段(實例),而event關鍵字則更像是一個修飾符——這就是錯覺的來源之一
訂閱事件的時候+=操作符后面可以是一個委托實例,這與委托實例的賦值方法語法相同,這也讓事件看起來像是一個委托字段——這是錯覺的又一來源
重申:事件的本質是加裝在委托字段上的一個“蒙板”(mask),是個起掩蔽作用的包裝器,這個用於阻擋非法操作的“蒙板”絕不是委托字段本身


為什么要使用委托類型來聲明事件?
站在source的角度來看,是為了表明source能對外傳遞哪些消息
站在subscriber的角度來看,它是一種約定,是為了約束能夠使用什么樣簽名的方法來處理(響應)事件
委托類型的實例將用於存儲(引用)事件處理器


對比事件和屬性
屬性不是字段——很多時候屬性是字段的包裝器,這個包裝器用來保護字段不被濫用
事件不是委托字段——它是委托字段的包裝器,這個包裝器用於保護委托字段不被濫用
包裝器永遠都不可能是被包裝的東西

下面給出一個參考實例

查看代碼

namespace EventExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();
            Waiter waiter = new Waiter();

            customer.Order += waiter.Action;//掛接事件。waiter的Action訂閱着customer的Order
            customer.Action();
            customer.PayTheBill();
        }
    }

    public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);//聲明一個委托類型,專門用來聲明事件,約束事件處理器


    public class OrderEventArgs : EventArgs//聲明用來傳遞消息的類,派生自EventArgs
    {

        public string DishName { get; set; }
        public string Size { get; set; }
    }


    public class Customer //事件發起者
    {
        public event OrderEventHandler Order;//聲明事件Order。用OrderEventHandler來約束事件
        
        public double Bill { get; set; }

        public void Walkin()
        {
            Console.WriteLine("Walk in the restaurant");
        }

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

        public void Think()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Let me think......");
                Thread.Sleep(1000);
            }

            if (this.Order != null)//等於空說明沒有人訂閱這個事件,會報異常
            {
                OrderEventArgs e = new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                e.Size = "large";
                this.Order.Invoke(this, e);
            }
        }

        public void Action()
        {
            Console.ReadLine();
            Walkin();
            Sitdown();
            Think();
        }
        
        public void PayTheBill()
        {
            Console.WriteLine("I Will pay ${0}.",this.Bill);
        }
    }

    public class Waiter  //事件的響應者Waiter
    {
        public void Action(Customer customer, OrderEventArgs e)//事件處理器
        {
            Console.WriteLine("I will serve you the dish - {0}", e.DishName);
            double price = 10;
            switch (e.Size)
            {
                case "small":
                    price = price * 0.5;
                    break;
                case "large":
                    price = price * 1.5;
                    break;
                default:
                    break;
            }
            customer.Bill += price;
        }
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM