国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

由淺入深理解C#中的事件

這篇具有很好參考價值的文章主要介紹了由淺入深理解C#中的事件。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

目錄

本文較長,給大家提供了目錄,可以直接看自己感興趣的部分。

前言

有關事件的概念

示例

?   簡單示例

?   標準 .NET 事件模式

?   使用泛型版本的標準 .NET 事件模式

?   補充

總結(jié)
參考

前言

前面介紹了C#中的委托,事件的很多部分都與委托類似。實際上,事件就像是專門用于某種特殊用途的簡單委托,事件包含了一個私有的委托,如下圖所示:

有關事件的私有委托需要了解的重要事項如下:

1、事件提供了對它的私有控制委托的結(jié)構(gòu)化訪問。我們無法直接訪問該委托。

2、事件中可用的操作比委托要少,對于事件我們只可以添加、刪除或調(diào)用事件處理程序。

3、事件被觸發(fā)時,它調(diào)用委托來依次調(diào)用調(diào)用列表中的方法。

有關事件的概念

發(fā)布者(Publisher):發(fā)布某個事件的類或結(jié)構(gòu),其他類可以在該事件發(fā)生時得到通知。

訂閱者(Subscriber):注冊并在事件發(fā)生時得到通知的類或結(jié)構(gòu)。

事件處理程序(event handler):由訂閱者注冊到事件的方法,在發(fā)布者觸發(fā)事件時執(zhí)行。

觸發(fā)(raise)事件:調(diào)用(invoke)或觸發(fā)(fire)事件的術語。當事件觸發(fā)時,所有注冊到它的方法都會被依次調(diào)用。

示例

簡單示例

現(xiàn)在我們先來看一下最最原始的事件示例。其結(jié)構(gòu)如下所示:

委托類型聲明:事件和事件處理程序必須有共同的簽名和返回類型,它們通過委托類型進行描述。

事件處理程序聲明:訂閱者類中會在事件觸發(fā)時執(zhí)行的方法聲明。它們不一定有顯示命名的方法,還可以是匿名方法或Lambda表達式。

事件聲明:發(fā)布者類必須聲明一個訂閱者類可以注冊的事件成員。當聲明的事件為public時,稱為發(fā)布了事件。

事件注冊:訂閱者必須訂閱事件才能在它被觸發(fā)時得到通知。

觸發(fā)事件的代碼:發(fā)布者類中”觸發(fā)“事件并導致調(diào)用注冊的所有事件處理程序的代碼。

現(xiàn)在我們可以照著這個思路去寫示例代碼。

首先聲明一個自定義的委托類型:

 public delegate void MyDelegate();

該委托類型沒有參數(shù)也沒有返回值。

然后再寫一個發(fā)布者類:

   public class Publisher
   {
       public event MyDelegate MyEvent;
       public void DoCount()
       {
           for(int i = 0; i < 10; i++) 
           { 
               Task.Delay(3000).Wait();            
                   
               //確認有方法可以執(zhí)行
               if(MyEvent != null)
               {
                    //觸發(fā)事件
                    MyEvent();
                }
               
           }
       }
   }

事件聲明:

 public event MyDelegate MyEvent;

事件聲明在一個類中,它需要委托類型的名稱,任何注冊到事件的處理程序都必須與委托類型的簽名和返回類型匹配。它聲明為public,這樣其他類和結(jié)構(gòu)可以在它上面注冊事件處理程序。不能使用對象創(chuàng)建表達式(new表達式)來創(chuàng)建它的對象。

一個常見的誤解就是把事件認為是類型,事件其實不是類型,它和方法、屬性一樣是類或結(jié)構(gòu)的成員。

由于事件是成員,所以我們不能在一段可執(zhí)行的代碼中聲明事件,它必須聲明在類或結(jié)構(gòu)中,和其他成員一樣。

事件成員被隱式自動初始化為null。

事件聲明的圖解如下所示:

觸發(fā)事件:

              //確認有方法可以執(zhí)行
               if(MyEvent != null)
               {
                    //觸發(fā)事件
                    MyEvent();
                }

也可以這樣寫:

              //確認有方法可以執(zhí)行
               if(MyEvent != null)
               {
                    //觸發(fā)事件
                    MyEvent().Invoke();
                }

這兩者是等效的,MyEvent();直接調(diào)用事件的委托,MyEvent().Invoke()使用顯式調(diào)用委托的 Invoke 方法。

現(xiàn)在再看看訂閱者類:

  public class Subscriber
  {          
      public void EventHandler()
      {
          Console.WriteLine($"{DateTime.Now}執(zhí)行了事件處理程序");
      }
  }

訂閱者類中有一個EventHandler方法,與前面定義的委托類型的簽名與返回值類型一致。

在看下主函數(shù):

 static void Main(string[] args)
 {
   Publisher publisher = new Publisher();
   Subscriber subscriber = new Subscriber();
   
   //訂閱事件
   publisher.MyEvent += subscriber.EventHandler;
   
   publisher.DoCount();
 }
 publisher.MyEvent += subscriber.EventHandler;

就是在訂閱事件,對應上面結(jié)構(gòu)圖中的事件注冊,將subscriber類的EventHandler方法注冊到publisher類的MyEvent事件上。

也可以通過:

 publisher.MyEvent -= subscriber.EventHandler;

取消訂閱事件。

運行結(jié)果如下所示:

本示例全部代碼如下所示:

 internal class Program
 {
     public delegate void MyDelegate();
     public class Publisher
     {
         public event MyDelegate MyEvent;
         public void DoCount()
         {
             for(int i = 0; i < 3; i++) 
             { 
                 Task.Delay(3000).Wait();
              
                 //確認有方法可以執(zhí)行
                 if(MyEvent != null)
                 {
                     //觸發(fā)事件
                     MyEvent();
                 }
                 
             }
         }
     }
     public class Subscriber
     {          
         public void EventHandler()
         {
             Console.WriteLine($"{DateTime.Now}執(zhí)行了事件處理程序");
         }
     }
     static void Main(string[] args)
     {
       Publisher publisher = new Publisher();
       Subscriber subscriber = new Subscriber();
       
       //訂閱事件
       publisher.MyEvent += subscriber.EventHandler;
       
       publisher.DoCount();
     }
 }

以上就根據(jù)上面的結(jié)構(gòu)圖寫出了一個使用事件的示例,但是本示例還有需要改進的地方。

上面我們觸發(fā)事件檢查空值是這樣寫的:

                 //確認有方法可以執(zhí)行
                 if(MyEvent != null)
                 {
                     //觸發(fā)事件
                     MyEvent();
                 }

C# 6.0 引入了空條件操作符之后,現(xiàn)在也可以這樣做空值檢查:

 MyEvent?.Invoke();

同時也不是一上來就檢查空值,而是先將MyEvent賦給第二個委托變量localDelegate:

 MyDelegate localDelegate = MyEvent;
 localDelegate?.Invoke();

這個簡單的修改可確保在檢查空值和發(fā)送通知之間,如果一個不同的線程移除了所有MyEvent訂閱者,將不會引發(fā)NullReferenceException異常。

標準 .NET 事件模式

以上我們以一個簡單的例子介紹了C#中的事件,但是大家可能會覺得有點模式,跟我們平常在winform中使用的事件好像不太一樣,那是因為 .NET 框架提供了一個標準模式,接下來我將以winform中的button按鈕點擊事件為例進行介紹。

頁面很簡單,只有一個button按鈕:

然后button按鈕點擊事件的代碼如下:

 private void button1_Click(object sender, EventArgs e)
 {
     MessageBox.Show("Hello World");
 }

現(xiàn)在我們再根據(jù)下面這張事件結(jié)構(gòu)圖,來看一看標準的 .NET 事件模式:

事件注冊

打開解決方案中的Form1.Designer.cs文件:

看到button1相關內(nèi)容:

button1.Click += button1_Click;

就是在訂閱事件,對應上面圖中的事件注冊。

委托類型聲明

右鍵查看定義:

 public event EventHandler? Click
 {
     add => Events.AddHandler(s_clickEvent, value);
     remove => Events.RemoveHandler(s_clickEvent, value);
 }

發(fā)現(xiàn)Click事件中的委托類型是EventHandler,再查看EventHandler的定義:

 public delegate void EventHandler(object? sender, EventArgs e);

這一步對應上面事件結(jié)構(gòu)圖中的委托類型聲明。

EventHandler是 .NET中預定義的委托,專門用來表示不生成數(shù)據(jù)的事件的事件處理程序方法應有的簽名與返回類型。

第一個參數(shù)是sender,用來保存觸發(fā)事件的對象的引用。由于是object?類型,所以可以匹配任何類型的實例。

第二個參數(shù)是e,用于傳遞數(shù)據(jù)。但是EventArgs類表示包含事件數(shù)據(jù)的類的基類,并提供用于不包含事件數(shù)據(jù)的事件的值。也就是說EventArgs設計為不能傳遞任何數(shù)據(jù)。它用于不需要傳遞數(shù)據(jù)的事件處理程序,通常會被忽略。如果我們想要傳遞數(shù)據(jù),必須聲明一個派生自EventArgs的類,使用合適的字段來保存需要傳遞的數(shù)據(jù)。

盡管EventArgs類實際上并不傳遞數(shù)據(jù),但它是使用EventHandler委托模式的重要部分。不管參數(shù)使用的實際類型是什么,object類和EventArgs類總是基類,這樣EventHandler就能提供一個對所有事件和事件處理器都通用的簽名,只允許兩個參數(shù),而不是各自都有不同簽名。

事件聲明

 public event EventHandler? Click
 {
     add => Events.AddHandler(s_clickEvent, value);
     remove => Events.RemoveHandler(s_clickEvent, value);
 }

Click事件在Control類中定義,Button類繼承自ButtonBase類,而ButtonBase類繼承自Control類。

public event EventHandler? Click;

對應上面結(jié)構(gòu)圖中的事件聲明。

觸發(fā)事件的代碼

查看Button類的定義,找到OnClick方法的定義:

 protected override void OnClick(EventArgs e)
 {
     Form? form = FindForm();
     if (form is not null)
     {
         form.DialogResult = _dialogResult;
     }

     // accessibility stuff
     AccessibilityNotifyClients(AccessibleEvents.StateChange, -1);
     AccessibilityNotifyClients(AccessibleEvents.NameChange, -1);

     // UIA events:
     if (IsAccessibilityObjectCreated)
     {
         AccessibilityObject.RaiseAutomationPropertyChangedEvent(UiaCore.UIA.NamePropertyId, Name, Name);
         AccessibilityObject.RaiseAutomationEvent(UiaCore.UIA.AutomationPropertyChangedEventId);
     }

     base.OnClick(e);
 }

去掉無關部分,保留相關部分便于理解:

 protected override void OnClick(EventArgs e)
 {
         base.OnClick(e);
}    

這里的base指的是Button類的基類ButtonBase類:

再查看ButtonBase類中OnClick方法的定義:

 protected override void OnClick(EventArgs e)
 {
     base.OnClick(e);
     OnRequestCommandExecute(e);
 }

發(fā)現(xiàn)也有一個base.OnClick(e);,這里的base指的是ButtonBase類的基類Control

再查看Control類中OnClick方法的定義:

 /// <summary>
 ///  Raises the <see cref="Click"/>
 ///  event.
 /// </summary>
 [EditorBrowsable(EditorBrowsableState.Advanced)]
 protected virtual void OnClick(EventArgs e)
 {
     ((EventHandler?)Events[s_clickEvent])?.Invoke(this, e);
 }

終于找到了觸發(fā)事件的代碼。

事件處理程序

這個想必大家并不陌生,雙擊button按鈕就可以看到:

  private void button1_Click(object sender, EventArgs e)
  {
      MessageBox.Show("Hello World");
  }

這對應上面結(jié)構(gòu)圖中的事件處理程序。該事件處理程序方法的簽名與返回值類型與EventHandler委托類型一致。

使用泛型版本的標準 .NET事件模式

接下來我會舉一個例子,說明如何使用泛型版本的標準 .NET事件模式。

第一步,自定義事件數(shù)據(jù)類,該類繼承自EventArgs類:

  public class MyEventArgs : EventArgs
  {
      public string? Message {  get; set; }
      public DateTime? Date {  get; set; }
  }

擁有兩個屬性Message與Date。

第二步,寫發(fā)布者類:

  public class Publisher
  {
      public event EventHandler<MyEventArgs>? SendMessageEvent;
      public void SendMessage()
      {
          for(int i = 0; i < 3; i++)
          {
              Task.Delay(3000).Wait();
              MyEventArgs e = new MyEventArgs();
              e.Message = $"第{i+1}次觸發(fā)事件";
              e.Date = DateTime.Now;
              EventHandler<MyEventArgs>? localEventHandler = SendMessageEvent;
              localEventHandler?.Invoke(this, e);
          }
      }
  }
public event EventHandler<MyEventArgs>? SendMessageEvent;

聲明了事件。

 EventHandler<MyEventArgs>? localEventHandler = SendMessageEvent;
 localEventHandler?.Invoke(this, e);

觸發(fā)了事件。

第三步,寫訂閱者類:

 public class Subscriber
 {
     public void EventHandler(object? sender,MyEventArgs e)
     {
         Console.WriteLine($"Received Message:{e.Message} at {e.Date}");
     }
 }

包含事件處理程序,該方法與EventHandler<MyEventArgs>委托類型的簽名與返回值類型一致。

第四步,寫主函數(shù):

  static void Main(string[] args)
  {
     Publisher publisher = new Publisher();
     Subscriber subscriber = new Subscriber();
     publisher.SendMessageEvent += subscriber.EventHandler;
     publisher.SendMessage();
  }
 publisher.SendMessageEvent += subscriber.EventHandler;

訂閱事件。

運行結(jié)果如下所示:

包含了我們自定義的事件數(shù)據(jù)。

補充

上面說自定義的事件數(shù)據(jù)類要繼承自EventArgs類,但其實在 .NET Core 的模式較為寬松。 在此版本中,EventHandler<TEventArgs> 定義不再要求 TEventArgs 必須是派生自 System.EventArgs 的類。

因此我在.NET 8 版本的示例中去掉繼承自EventArgs類,該示例依舊能正常運行。

異步事件訂閱者

一個關于異步事件訂閱者的例子如下:

// 事件發(fā)布者
public class EventPublisher
{
    // 定義異步事件
    public event Func<string, Task>? MyEvent;

    // 觸發(fā)事件的方法
    public async Task RaiseEventAsync(string message)
    {
        Func<string, Task> localEvent = MyEvent;
        await localEvent?.Invoke(message);
    }
}

// 異步事件訂閱者
public class AsyncEventSubscriber
{
    // 處理事件的異步方法
    public async Task HandleEventAsync(string message)
    {
        Console.WriteLine($"Received event with message: {message}");

        // 異步操作,例如IO操作、網(wǎng)絡請求等
        await Task.Delay(3000);

        Console.WriteLine("Event handling complete.");
    }
}

class Program
{
    static async Task Main(string[] args)
    {
        // 創(chuàng)建事件發(fā)布者
        var publisher = new EventPublisher();

        // 創(chuàng)建異步事件訂閱者
        var subscriber = new AsyncEventSubscriber();

        // 訂閱事件
        publisher.MyEvent += subscriber.HandleEventAsync;

        // 觸發(fā)事件
        await publisher.RaiseEventAsync("Hello, world!");

        Console.ReadLine();
    }
}

運行結(jié)果如下所示:

總結(jié)

本文先是介紹了一些C#中事件的相關概念,然后通過幾個例子介紹了在C#中如何使用事件。

參考

1、《C#圖解教程》

2、《C# 7.0 本質(zhì)論》

3、[C# 文檔 - 入門、教程、參考。 | Microsoft Learn]文章來源地址http://www.zghlxwxcb.cn/news/detail-777060.html

到了這里,關于由淺入深理解C#中的事件的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權(quán),不承擔相關法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • Docker由淺入深(一)

    容器化技術介紹 介紹容器化之前,我們得先知道,為什么會出現(xiàn)容器化,容器化之前都經(jīng)歷了什么 物理機時代 部署非常慢 成功很高 浪費資源 難于擴展與遷移 受制于硬件 虛擬化時代 在同一個物理機上安裝多個虛擬機,每個虛擬機安裝操作系統(tǒng)和應用, 虛擬機之間物理資源

    2024年02月03日
    瀏覽(39)
  • 由淺入深了解HashMap源碼

    由淺入深了解HashMap源碼

    ? ? ? ?由經(jīng)典面試題引入,講解一下HashMap的底層數(shù)據(jù)結(jié)構(gòu)?這個面試題你當然可以只答,HashMap底層的數(shù)據(jù)結(jié)構(gòu)是由(數(shù)組+鏈表+紅黑樹)實現(xiàn)的,但是顯然面試官不太滿意這個答案,畢竟這里有一個坑需要你去填,那就是在回答HashMap的底層數(shù)據(jù)結(jié)構(gòu)時需要考慮JDK的版本,因

    2023年04月13日
    瀏覽(27)
  • 由淺入深Netty代碼調(diào)優(yōu)

    由淺入深Netty代碼調(diào)優(yōu)

    序列化,反序列化主要用在消息正文的轉(zhuǎn)換上 序列化時,需要將 Java 對象變?yōu)橐獋鬏數(shù)臄?shù)據(jù)(可以是 byte[],或 json 等,最終都需要變成 byte[]) 反序列化時,需要將傳入的正文數(shù)據(jù)還原成 Java 對象,便于處理 目前的代碼僅支持 Java 自帶的序列化,反序列化機制,核心代碼如

    2024年02月05日
    瀏覽(23)
  • React - redux 使用(由淺入深)

    React - redux 使用(由淺入深)

    中文文檔: http://www.redux.org.cn/ 英文文檔: https://redux.js.org/ Github: https://github.com/reactjs/redux 可直接參照 目錄十 進行使用 react-redux redux 是一個專門用于做狀態(tài)管理的JS庫(不是react插件庫)。 它可以用在 react, angular, vue 等項目中, 但基本與 react 配合使用。 作用: 集中式管理 re

    2024年02月07日
    瀏覽(24)
  • 【個人筆記】由淺入深分析 ClickHouse

    項目中不少地方使用到ClickHouse,就對它做了一個相對深入一點的了解和研究。并對各種知識點及整理過程中的一些理解心得進行了匯總并分享出來,希望對其他同學能有幫助。 本文主要講解ClickHouse的特點、讀寫過程、存儲形式、索引、引擎、物化視圖等特性。 適合 入門和

    2024年01月20日
    瀏覽(28)
  • 【由淺入深學習MySQL】之索引進階

    【由淺入深學習MySQL】之索引進階

    本系列為:MySQL數(shù)據(jù)庫詳解,為千鋒資深教學老師獨家創(chuàng)作 致力于為大家講解清晰MySQL數(shù)據(jù)庫相關知識點,含有豐富的代碼案例及講解。如果感覺對大家有幫助的話,可以【關注】持續(xù)追更~ 文末有本文重點總結(jié),技術類問題,也歡迎大家和我們溝通交流! 從今天開始本系列

    2024年02月05日
    瀏覽(22)
  • 手拉手Vue組件由淺入深

    手拉手Vue組件由淺入深

    組件 (Component) 是 Vue.js 最強大的功能之一,它是html、css、js等的一個聚合體,封裝性和隔離性非常強。 組件化開發(fā): ??? 1、將一個具備完整功能的項目的一部分分割多處使用 ??? 2、加快項目的進度 ??? 3、可以進行項目的復用 組件注冊分為:全局注冊和局部注冊 目錄

    2024年01月18日
    瀏覽(22)
  • 由淺入深介紹 Python Websocket 編程

    由淺入深介紹 Python Websocket 編程

    1.1 websocket 協(xié)議簡介 Websocket協(xié)議是對http的改進,可以實現(xiàn)client 與 server之間的雙向通信; websocket連接一旦建立就始終保持,直到client或server 中斷連接,彌補了http無法保持長連接的不足,方便了客戶端應用與服務器之間實時通信。 適用場景 html頁面實時更新, 客戶端的html頁面

    2024年02月03日
    瀏覽(22)
  • Springboot3+EasyExcel由淺入深

    Springboot3+EasyExcel由淺入深

    環(huán)境介紹 技術棧 springboot3+easyexcel 軟件 版本 IDEA IntelliJ IDEA 2022.2.1 JDK 17 Spring Boot 3 EasyExcel是一個基于Java的、快速、簡潔、解決大文件內(nèi)存溢出的Excel處理工具。 他能讓你在不用考慮性能、內(nèi)存的等因素的情況下,快速完成Excel的讀、寫等功能。 官網(wǎng)https://easyexcel.opensource.ali

    2024年01月16日
    瀏覽(28)
  • 【由淺入深學MySQL】- MySQL連接查詢

    【由淺入深學MySQL】- MySQL連接查詢

    本系列為:MySQL數(shù)據(jù)庫詳解,為千鋒教育資深Java教學老師獨家創(chuàng)作 致力于為大家講解清晰MySQL數(shù)據(jù)庫相關知識點,含有豐富的代碼案例及講解。如果感覺對大家有幫助的話,可以【點個關注】持續(xù)追更~ 文末有重點總結(jié)和福利內(nèi)容! 技術類問題,也歡迎大家和我們溝通交流!

    2024年02月05日
    瀏覽(23)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包