香蕉视频app

香蕉视频appKeep on going never give up.

Let's Go

C# 學習筆記(51)事件

C#Lonely2019-05-28 03:03:02142次0條

C#事件(Event)

事件(Event) 基本上說是一個用戶操作,如按鍵、點擊、鼠標移動等等,或者是一些出現,如系統生成的通知。應用程序需要在事件發生時響應事件。例如,中斷。事件是用于進程間通信。

事件涉及兩類角色——事件發布者事件訂閱者當某個事件發生后,事件發布者會發布消息;事件訂閱者會收到事件已發生的通知,并做出相應的處理。其中,觸發事件的對象稱為事件發布者,捕獲事件并對其做出處理的對象稱為事件訂閱者。

事件在類中聲明且生成,且通過使用同一個類或其他類中的委托與事件處理程序關聯。包含事件的類用于發布事件。這被稱為 發布器(publisher)類。其他接受該事件的類被稱為訂閱器(subscriber)類。事件使用 發布-訂閱(publisher-subscriber)模型。

發布器(publisher)是一個包含事件和委托定義的對象。事件和委托之間的聯系也定義在這個對象中。發布器(publisher)類的對象調用這個事件,并通知其他的對象。

香蕉视频app 訂閱器(subscriber)是一個接受事件并提供事件處理程序的對象。在發布器(publisher)類中的委托調用訂閱器(subscriber)類中的方法(事件處理程序)。

事件定義的語法:

訪問修飾符 event 委托類型 事件名;

這里,訪問修飾符一般定義為public,因為事件的訂閱者需要對事件進行訂閱與取消操作,定義為公共類型可使事件對其他類可見。事件定義中還包含委托類型,它既可以是自定義的委托類型,也可以是.NET類庫中預定義的委托類型EventHandler。

訂閱和取消事件

事件訂閱者需要訂閱 事件發布者 發布的事件,以便在事件被觸發時接收消息并做出處理。使用“+=”運算符來訂閱事件,使用“-=”運算符來取消事件訂閱

香蕉视频app 代碼示例(模擬音樂播放完畢后觸發的事件):

using System;
using System.Threading;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Music music = new Music("恭喜發財-劉德華"); //創建對象
            music.playOver += Music_playOver; //綁定事件
            music.PlayMusic(); //開始播放音樂
            Console.ReadKey();
        }

        //事件處理函數,該函數需要符合自定義委托 DelPlayOver 的定義
        private static void Music_playOver()
        {
            Console.WriteLine("歌曲播放完畢了!");
        }
    }

    public class Music
    {
        //例子:模擬音樂播放完畢后觸發的事件

        public delegate void DelPlayOver(); //自定義委托

        //使用自定義委托類型定義事件,事件名為playOver
        public event DelPlayOver playOver; 

        public string MusicName { get; set; } //字段

        public Music(string musicName) //構造函數
        {
            this.MusicName = musicName;
        }

        public void PlayMusic()
        {
            Console.WriteLine("正在播放:{0}", this.MusicName);

            // Thread.Sleep 使當前線程停止一段時間運行
            Thread.Sleep(5000); // 模擬播放歌曲,5秒后播放完畢

            //判斷是否綁定了事件處理方法
            if (playOver != null)
            {
                //歌曲播放完畢后觸發事件
                playOver();
            }
        }
    }

}

運行結果:

image.png

上面的代碼演示了使用自定義委托類型來定義事件的例子,值得注意的是,事件處理函數的定義需要與委托的定義保持一致,即其參數個數、參數類型和返回類型等需要與委托相同。香蕉视频app除了可以使用自定義委托類型來定義事件外,還可以使用.NET類型中預定義的委托類型EventHandler來定義事件,后者也是實際開發中普遍采用的一種方式。

香蕉视频app 代碼示例:

using System;
using System.Threading;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Music music = new Music("恭喜發財-劉德華"); //創建對象
            music.playOver += Music_playOver; //綁定事件
            music.PlayMusic(); //開始播放音樂
            Console.ReadKey();
        }

        /// <summary>
        /// 事件處理函數,該函數需要符合委托 EventHandler 的定義
        /// </summary>
        /// <param name="sender">事件源(觸發當前事件的對象)</param>
        /// <param name="e">不包含事件數據的對象</param>
        private static void Music_playOver(object sender, EventArgs e)
        {
            Console.WriteLine("歌曲播放完畢了!");
        }
    }

    public class Music
    {
        //例子:模擬音樂播放完畢后觸發的事件

        //使用.NET類庫中預定義的委托類型來定義事件
        public event EventHandler playOver; 
        
        public string MusicName { get; set; } //字段

        public Music(string musicName) //構造函數
        {
            this.MusicName = musicName;
        }

        public void PlayMusic()
        {
            Console.WriteLine("正在播放:{0}", this.MusicName);

            // Thread.Sleep 使當前線程停止一段時間運行
            Thread.Sleep(5000); // 模擬播放歌曲,5秒后播放完畢

            //判斷是否綁定了事件處理方法
            if (playOver != null)
            {
                //歌曲播放完畢后觸發事件
                playOver(this, new EventArgs());
            }
        }
    }

}

以上代碼運行結果與上一致。EventHandler是.NET類庫中預定義的委托類型,用于處理不包含事件數據的事件。如果想在事件中包含事件數據,可以通過派生EventArgs來實現。下面,先讓我們看看EventHandler委托的定義,你可以通過在代碼中把光標移到EventHandler處,再按F12快捷鍵的方式來查看,它的具體定義如下:

image.png

香蕉视频app 從EventHandler委托的定義可以看出:

香蕉视频app 該委托的返回類型為void,因此實例化委托的方法也需要滿足這點;

香蕉视频app 第一個參數sender負責保存對觸發事件對象的引用,其類型為object;

香蕉视频app 第二個參數e負責保存事件數據,EventArgs類也是.NET類庫中定義的類,它不保存任何數據。


擴展EventArgs類

因為EventHandler只用于處理不包含事件數據的事件,如果我們想要在由這種方式定義的事件中包含事件數據,則可以通過派生EventArgs類來實現。下面通過擴展EventArgs類來使事件參數e帶有事件數據,具體實現代碼如下:

using System;
using System.Threading;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Music music = new Music("恭喜發財-劉德華"); //創建對象
            music.playOver += Music_playOver; //綁定事件
            music.PlayMusic(); //開始播放音樂
            Console.ReadKey();
        }

        //事件處理函數
        private static void Music_playOver(object sender, MyEventArgs e)
        {
            Console.WriteLine(e.Message);
        }
    }

    //自定義事件類,并使其帶有事件數據
    public class MyEventArgs : EventArgs
    {
        public string Message { get; set; }
        public MyEventArgs(string msg)
        {
            this.Message = msg;
        }
    }

    public class Music
    {
        //例子:模擬音樂播放完畢后觸發的事件

        //自定義委托類型,委托包含兩個參數
        public delegate void DelplayOver(object sender, MyEventArgs e);

        //定義事件
        public event DelplayOver playOver; 
        
        public string MusicName { get; set; } //字段

        public Music(string musicName) //構造函數
        {
            this.MusicName = musicName;
        }

        public void PlayMusic()
        {
            Console.WriteLine("正在播放:{0}", this.MusicName);

            // Thread.Sleep 使當前線程停止一段時間運行
            Thread.Sleep(5000); // 模擬播放歌曲,5秒后播放完畢

            string msg = "歌曲:" + this.MusicName + ",播放完畢了!";

            //判斷是否綁定了事件處理方法
            if (playOver != null)
            {
                //歌曲播放完畢后觸發事件
                playOver(this, new MyEventArgs(msg));
            }
        }
    }   
}

香蕉视频app 運行結果:

image.png


事件的本質

從事件的使用過程可以看出,事件的定義中包含了委托類型。那么,事件與委托之間到底有著什么樣的關系呢?下面通過探究事件所產生的IL代碼來說明它們之間的關系。我們在一個類中定義了如下事件:

using System;

namespace ConsoleApp
{
    class Program
    {
        public event EventHandler MyEvent;

        static void Main(string[] args)
        {
           
        }
    }
}

將以上代碼生成所示IL代碼,如下圖所示:

image.png

由上IL代碼可以看出,C#事件被編譯成包含兩個公共方法的代碼段,一個帶有add_前綴,另一個帶有remove_前綴,前綴后面是C#事件的名稱。查看add_MyEvent()的IL代碼,你會發現add_MyEvent()方法是通過調用Delegate.Combine()方法來實現的,Delegate.Combine()方法將多個委托組合為了一個多路廣播委托,下面給出了add_MyEvent()的IL代碼與反編譯后的C#代碼,如下所示:

image.png

image.png

相應地,remove_MyEvent方法也將間接調用Delegate.Remove()方法,Delegate.Remove()方法用于將一個委托從多路廣播委托中移除。

image.png

image.png

其中還有一個MyEvent字段的定義,查看其IL代碼內容如下:

image.png

從以上分析可以得出,C#中的事件的本質是一個特殊的多路廣播委托,事件默認含有一個私有的委托類型變量,該變量用于保存對事件處理方法的引用,且該委托類型的變量為私有,只能從定義該事件的類中進行訪問(只能在類內部調用(觸發)事件)。C#事件提供了對私有委托字段進行訪問的有效方法。

由事件的IL代碼段,我們可以聯想到類中屬性的定義。與事件不同,屬性中定義了set訪問器和get訪問器,兩個訪問器的本質是以“get_”和“set_”為前綴的兩個方法。屬性用于對類中的私有字段進行訪問,而C#事件也可以看作是“委托字段的屬性”,因此可以通過事件來對私有的委托字段進行訪問,這也是C#事件特性存在的原因。C#事件機制符合面向對象的封裝特性,它使得應用程序代碼更加安全。



暗錨,解決錨點偏移

文章評論

    嘿,來試試登錄吧!