C#基礎篇——事件

来源:https://www.cnblogs.com/i3yuan/archive/2020/06/12/13081631.html
-Advertisement-
Play Games

前言 在本章中,主要是藉機這個C#基礎篇的系列整理過去的學習筆記、歸納總結並更加理解透徹。 在上一篇文章,我們已經對委托有了進一步瞭解,委托相當於用方法作為另一方法參數,同時,也可以實現在兩個不能直接調用的方法中做橋梁。 下麵我們來回顧一下委托的例子。 public delegate void Ex ...


前言

在本章中,主要是藉機這個C#基礎篇的系列整理過去的學習筆記、歸納總結並更加理解透徹。

在上一篇文章,我們已經對委托有了進一步瞭解,委托相當於用方法作為另一方法參數,同時,也可以實現在兩個不能直接調用的方法中做橋梁。

下麵我們來回顧一下委托的例子。

    public delegate void ExecutingDelegate(string name);

    public class ExecutingManager
    {
        public void ExecuteProgram(string name, ExecutingDelegate ToExecuting)
        {
            ToExecuting(name);
        }
    }
    private static void StartExecute(string name)
    {
        Console.WriteLine("開始執行:" + name);
    }

    private static void EndExecute(string name)
    {
        Console.WriteLine("結束執行:" + name);
    }

    static void Main(string[] args)
    {
        ExecutingManager exec = new ExecutingManager();
        exec.ExecuteProgram("開始。。。", StartExecute);
        exec.ExecuteProgram("結束。。。", EndExecute);
        Console.ReadKey();
    }

根據上述的示例,再利用上節學到的知識,將多個方法綁定到同一個委托變數實現多播,該如何做呢?

再次修改代碼:

    static void Main(string[] args)
    {
        ExecutingManager exec = new ExecutingManager();
        ExecutingDelegate executingDelegate;
        executingDelegate = StartExecute;
        executingDelegate += EndExecute;
        exec.ExecuteProgram("yuan", executingDelegate);

        Console.ReadKey();
    }

但是,此刻我們發現是不是可以將實例化聲明委托的變數封裝到ExecutingManager類中,這樣是不是更加方便調用呢?

    public class ExecutingManager
    {
        /// <summary>
        /// 在 ExecutingManager 類的內部聲明 executingDelegate 變數
        /// </summary>
        public ExecutingDelegate executingDelegate;
        public void ExecuteProgram(string name, ExecutingDelegate ToExecuting)
        {
            ToExecuting(name);
        }
    }
    static void Main(string[] args)
    {
        ExecutingManager exec = new ExecutingManager();
        exec.executingDelegate = StartExecute;
        exec.executingDelegate += EndExecute;
        exec.ExecuteProgram("yuan", exec.executingDelegate);
        Console.ReadKey();
    }

寫到這裡了,這樣做沒有任何問題,但我們發現這條語句很奇怪。在調用exec.ExecuteProgram方法的時候,再次傳遞了exec的executingDelegate欄位, 既然如此,我們何不修改 ExecutingManager類成這樣:

    public class ExecutingManager
    {
        /// <summary>
        /// 在 GreetingManager 類的內部聲明 delegate1 變數
        /// </summary>
        public ExecutingDelegate executingDelegate;
        public void ExecuteProgram(string name)
        {
            if (executingDelegate != null) // 如果有方法註冊委托變數
            {
                executingDelegate(name); // 通過委托調用方法
            }
        }
    }
    static void Main(string[] args)
    {
        ExecutingManager exec = new ExecutingManager();
        exec.executingDelegate = StartExecute;
        exec.executingDelegate += EndExecute;
        exec.ExecuteProgram("yuan");
        Console.ReadKey();
    }

這樣再看,發現調用一下就更加簡潔了。

正文

在日常生活中,我們可能都會遇到這樣的各種各樣的事情,而對於這些事情我們都會採取相應的措施。比如,當你要給一個女神過生日的時候,你就可以給她送禮物。而這種情況,在C#開發中,就相當於過生日被當作事件來對待,而送禮物就是事件做出的響應。

當女神過生日的時候,女神就會發佈生日事件,而你就會接受到這個事件的通知,並做出響應的處理(送禮物等騷操作)。其中,觸發這個事件的對象我們可稱之為事件發佈者,而捕獲這個事件並做出相應處理的稱之為事件訂閱者,我們可以看出,女神就是充當了發佈者,而你自己則充當了訂閱者。

這裡由生日事件引申出兩類角色,即事件發佈者事件訂閱者

開始

1.發佈者/訂閱者模式

在開發中,我們是否遇到這樣的情景,當一個特定的程式事件發生時,其他程式部分可以得到該事件註冊發生通知。

發佈者定義一系列事件,並提供一個註冊方法;訂閱者向發佈者註冊,並提供一個可被回調的方法,也就是事件處理程式;當事件被觸發的時候,訂閱者得到通知,而訂閱者所提交的所有方法會被執行。

  • 發佈者:發佈某個事件的類或結構,其他類可以在該事件發生時得到通知。
  • 訂閱者:註冊併在事件發生時得到通知的類或結構。
  • 事件處理程式:由訂閱者註冊到事件的方法,在發佈者觸發事件時執行。事件處理程式方法可以定義在事件所在的類或結果中,也可以定義在不同的類或結構中。
  • 觸發事件:調用事件的術語。當事件觸發時,所有註冊到它的方法都會被一次調用。

2.基本使用

        /// <summary>
        /// 先自定義一個委托
        /// </summary>
        /// <param name="oldPrice"></param>
        /// <param name="newPrice"></param>
        public delegate void PriceChangedHandler(decimal oldPrice, decimal newPrice);
        /// <summary>
        /// 這個一個發佈者
        /// </summary>
        public class IPhone
        {
            decimal price;
            /// <summary>
            /// 定義一個事件
            /// event 用來定義事件
            /// PriceChangedHandler委托類型,事件需要通過委托來調用訂閱者需要的方法
            /// </summary>
            public event PriceChangedHandler PriceChanged;
          
            public decimal Price
            {
                get { return price; }
                set
                {
                    if (price == value)
                        return;
                    decimal oldPrice = price;
                    price = value;             // 如果調用列表不為空,則觸發。            
                    if (PriceChanged != null)   //用來判斷事件是否被訂閱者註冊過
                        PriceChanged(oldPrice, price);   //調用事件
                }
            }
        }
        /// <summary>
        /// 這個一個訂閱者
        /// </summary>
        /// <param name="oldPrice"></param>
        /// <param name="price"></param>
        static void iPhone_PriceChanged(decimal oldPrice, decimal price)
        {
            Console.WriteLine("618促銷活動,全場手機 只賣 " + price + " 元, 原價 " + oldPrice + " 元,快來搶!");
        }
        static void Main()
        {
            ///實例化一個發佈者類
            IPhone phone = new IPhone()
            {
                Price = 5288
            };         // 訂閱事件   
            phone.PriceChanged += iPhone_PriceChanged;          //完成事件的註冊 調整價格(事件發生)    
            phone.Price = 3999;                                //激發事件,並調用事件
            Console.ReadKey();
        }

輸出:

618促銷活動,全場手機 只賣 3999 元, 原價 5288 元,快來搶!

3.解析

  1. 委托類型聲明:事件與事件處理程式必須有共同的簽名和返回類型,它們通過委托類型進行描述。
  2. 事件聲明:使用關鍵字evet來聲明一個事件,當聲明的事件為一個public時,稱為發佈了一個事件。
  3. 事件註冊:訂閱者通過+=操作符來註冊事件,並提供一個事件處理程式。
  4. 事件處理程式: 訂閱者向事件註冊的方法,它可以是顯示命名的方法、匿名方法或者Lambda表達式
  5. 觸發事件:發佈者用來調用事件的代碼

4.語法

事件的聲明語法:

//聲明一個事件
public [static] event EventHandler EventName;
//聲明多個同類型的事件
public [static] event EventHandler EventName1, EventName2, EventName3;

事件必須聲明在類或結構中,因為事件它不是一個類型,它是一個類或者結構中的一員。

在事件被觸發之前,可以通過和null做比較,判斷是否包含事件註冊處理程式。因為事件成員被初始化預設是null。

委托類型EventHandler是聲明專門用來事件的委托。事件提供了對委托的結構化訪問;也即是無法直接訪問事件中的委托。

5.用法

img

查看源碼:

事件的標準模式就是System命名空間下聲明的EventHandler委托類型。

EventArgs是System下的一個類,如下:

using System.Runtime.InteropServices;

namespace System
{
    [Serializable]
    [ComVisible(true)]
    [__DynamicallyInvokable]
    public class EventArgs
    {
        [__DynamicallyInvokable]
        public static readonly EventArgs Empty = new EventArgs();

        [__DynamicallyInvokable]
        public EventArgs()
        {
        }
    }
}

根據EventArgs源碼看出,EventArgs本身無法保存和傳遞數據的。

如果想保存和傳遞數據,可以實現一個EventArgs的派生類,然後定義相關的欄位來保存和傳遞參數。

    public class IPhone
    {
        decimal price;
        /// <summary>
        /// 使用EventHandler定義一個事件
        /// </summary>
        public event EventHandler PriceChanged;
        protected virtual void OnPriceChanged()
        {
            if (PriceChanged != null)
                PriceChanged(this, null);
        }
        public decimal Price
        {
            get { return price; }
            set
            {
                if (price == value) return;
                decimal oldPrice = price; 
                price = value;             // 如果調用列表不為空,則觸發。      
                if (PriceChanged != null)  // //用來判斷事件是否被訂閱者註冊過
                    OnPriceChanged();
            }
        }
    }
    /// <summary>
    /// 這個一個訂閱者
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    static void iphone_PriceChanged(object sender, EventArgs e)
    {
        Console.WriteLine("年終大促銷,快來搶!");
    }
    static void Main()
    {
        IPhone phone = new IPhone()
        {
            Price = 5288M
        };         // 訂閱事件  
        phone.PriceChanged += iphone_PriceChanged;
        // 調整價格(事件發生)   
        phone.Price = 3999;
        Console.ReadKey();
    }

通過擴展EventHanlder來傳遞數據

img

System下另有泛型EventHandler類。由此,這裡我們可以將派生於EventArgs的類作為類型參數傳遞過來,這樣,既可以獲得派生類保存的數據。

    ///擴展類
    public class PriceChangedEventArgs : System.EventArgs
    {
        public readonly decimal OldPrice;
        public readonly decimal NewPrice;
        public PriceChangedEventArgs(decimal oldPrice, decimal newPrice)
        {
            OldPrice = oldPrice;
            NewPrice = newPrice;
        }
    }
    public class IPhone
    {
        decimal price;
        public event EventHandler<PriceChangedEventArgs> PriceChanged;
        protected virtual void OnPriceChanged(PriceChangedEventArgs e)
        {
            if (PriceChanged != null)
                PriceChanged(this, e);
        }
        public decimal Price
        {
            get { return price; }
            set
            {
                if (price == value) return;
                decimal oldPrice = price;
                price = value;             // 如果調用列表不為空,則觸發。      
                if (PriceChanged != null)
                    OnPriceChanged(new PriceChangedEventArgs(oldPrice, price));
            }
        }
    }
    static void iphone_PriceChanged(object sender, PriceChangedEventArgs e)
    {
        Console.WriteLine("618促銷活動,全場手機 只賣 " + e.NewPrice + " 元, 原價 " + e.OldPrice + " 元,快來搶!");
    }
    static void Main()
    {
        IPhone phone = new IPhone()
        {
            Price = 5288M
        };         // 訂閱事件  
        phone.PriceChanged += iphone_PriceChanged;
        // 調整價格(事件發生)   
        phone.Price = 3999;
        Console.ReadKey();
    }

輸出

618促銷活動,全場手機 只賣 3999 元, 原價 5288 元,快來搶!

6.移除事件

可以利用 -= 運算符處理程式從事件中移除,當程式處理完後,可以將事件從中把它移除掉。

        class Publiser
        {
            public event EventHandler SimpleEvent;

            public void RaiseTheEvent()
            {
                SimpleEvent(this, null);
            }
        }

        class Subscriber
        {
            public void MethodA(object o, EventArgs e) { Console.WriteLine("A"); }
            public void MethodB(object o, EventArgs e) { Console.WriteLine("B"); }
        }


        static void Main(string[] args)
        {
            Publiser p = new Publiser();
            Subscriber s = new Subscriber();

            p.SimpleEvent += s.MethodA;
            p.SimpleEvent += s.MethodB;
            p.RaiseTheEvent();

            Console.WriteLine("\n移除B事件處理程式");
            p.SimpleEvent -= s.MethodB;
            p.RaiseTheEvent();

            Console.ReadKey();
        }

輸出:

img

7.事件訪問器

運算符+= 、-=事件允許的唯一運算符。這些運算符是有預定義的行為。然而,我們可以修改這些運算符的行為,讓事件執行任何我們希望定義的代碼。

可以通過為事件定義事件訪問器,來控制事件運算符+=、-=運算符的行為

  1. 兩個訪問器: add 和 remove
  2. 聲明事件的訪問器看上去和聲明一個熟悉差不多。

下麵示例演示了具有訪問器的聲明.兩個訪問器都有叫做value的隱式值參數,它接受實例或靜態方法的引用

public event EventHandler Elapsed
{
    add
    {
        //... 執行+=運算符的代碼
    }

     remove
     {
        //... 執行-=運算符的代碼
     }

}

聲明瞭事件訪問器後,事件不包含任何內嵌委托對象.我們必須實現自己的機制來存儲和移除事件的方法。

事件訪問器表現為void方法,也就是不能使用會返回值的return語句。

示例:

        //聲明一個delegate
        delegate void EventHandler();

        class MyClass
        {
            //聲明一個成員變數來保存事件句柄(事件被激發時被調用的delegate)
            private EventHandler m_Handler = null;

            //激發事件
            public void FireAEvent()
            {
                if (m_Handler != null)
                {
                    m_Handler();
                }
            }

            //聲明事件
            public event EventHandler AEvent
            {
                //添加訪問器
                add
                {
                    //註意,訪問器中實際包含了一個名為value的隱含參數
                    //該參數的值即為客戶程式調用+=時傳遞過來的delegate
                    Console.WriteLine("AEvent add被調用,value的HashCode為:" + value.GetHashCode());
                    if (value != null)
                    {
                        //設置m_Handler域保存新的handler
                        m_Handler = value;
                    }
                }

                //刪除訪問器
                remove
                {
                    Console.WriteLine("AEvent remove被調用,value的HashCode為:" + value.GetHashCode());
                    if (value == m_Handler)
                    {
                        //設置m_Handler為null,該事件將不再被激發
                        m_Handler = null;
                    }
                }

            }

        }

        static void Main(string[] args)
        {
            MyClass obj = new MyClass();
            //創建委托
            EventHandler MyHandler = new EventHandler(MyEventHandler);
            MyHandler += MyEventHandle2;
            //將委托註冊到事件
            obj.AEvent += MyHandler;
            //激發事件
            obj.FireAEvent();
            //將委托從事件中撤銷
            obj.AEvent -= MyHandler;
            //再次激發事件
            obj.FireAEvent();


            Console.ReadKey();
        }
        //事件處理程式
        static void MyEventHandler()
        {
            Console.WriteLine("This is a Event!");
        }

        //事件處理程式
        static void MyEventHandle2()
        {
            Console.WriteLine("This is a Event2!");
        }

輸出:

img

總結

  1. 這節對事件的基本使用,以及事件的標準語法、事件訪問器等多個地方進行說明,大致可以瞭解和掌握事件的基本使用。
  2. 結合上一篇的委托和這一節的事件,委托和事件我們大概掌握了基本用法。並加以實踐,結合實際開發,應用其中。
  3. 如果有不對的或不理解的地方,希望大家可以多多指正,提出問題,一起討論,不斷學習,共同進步。

參考 文檔 《C#圖解教程》

註:搜索關註公眾號【DotNet技術谷】--回覆【C#圖解】,可獲取 C#圖解教程文件


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 常見的IIS部署WebService,或者開發時,調試WebService的問題 1、想通過瀏覽器進行調用測試,比較快速方便 VS直接運行,還可以選用自己喜歡的瀏覽器進行調試,前提需要在 Web.Config配置文件中,增加配置信息 system.web節點下增加如下: <webServices> ...
  • CheckBox 選中預設的樣式是一個勾,如圖所示: 那麼我們怎麼樣才能修改這個 "勾" 呢? 我們只需要改變它的模板樣式即可,如下所示: 我們只需要在 Blend中 畫一個圖形,然後將數據替換掉 Data中的數據即可,如下所示,我畫的是一個愛心: 運行效果如下: ...
  • Asp.Net WebApi 上傳文件方法 實現功能: 1.原生js調用api上傳 2.jq ajax調用api上傳 後端 Model /// <summary> /// 上傳文件(如果遇到不明白的或者發現BUG請加入QQ群:Java .Net Go PHP UI群:574879752 直接@群主) ...
  • @ 簡介 什麼是Dikeko.ORM? Dikeko.ORM是一個簡單的.NET輕量級的ORM,目前僅支持SqlServer資料庫。 安裝 .NET版:https://www.nuget.org/packages/Dikeko.ORM PM>Install-Package Dikeko.ORM .N ...
  • Blazor WebAssembly可以在瀏覽器上跑C#代碼,但是很多時候顯然還是需要跟JavaScript打交道。比如操作dom,當然跟angular、vue一樣不提倡直接操作dom;比如瀏覽器的後退導航。反之JavaScript也有可能需要調用C#代碼來實現一些功能,畢竟客戶的需求是千變萬化的, ...
  • ps:本文需要先把abp的源碼下載一份來下,跟著一起找實現,更容易懂 在abp中,對於許可權和菜單使用靜態來管理,菜單的載入是在登陸頁面的地方(具體是怎麼知道的,瀏覽器按F12,然後去sources中去找) 這個/AbpScripts/GetScripts是獲取需要初始化的script,源自AbpSc ...
  • 本文基於 AutoMapper 9.0.0 AutoMapper 是一個對象-對象映射器,可以將一個對象映射到另一個對象。 官網地址:http://automapper.org/ 官方文檔:https://docs.automapper.org/en/latest/ 1 入門例子 public cl ...
  • 系列文章 基於 abp vNext 和 .NET Core 開發博客項目 - 使用 abp cli 搭建項目 基於 abp vNext 和 .NET Core 開發博客項目 - 給項目瘦身,讓它跑起來 基於 abp vNext 和 .NET Core 開發博客項目 - 完善與美化,Swagger登場 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...