1. 程式人生 > 其它 >Event事件和釋出者/訂閱者模式

Event事件和釋出者/訂閱者模式

很多程式都有這樣的一個需求,當一個特定的事件發生時,程式的其他部分能夠得到通知,並且需要做一些事情。這個時候就需要事件了。

釋出者/訂閱者模式

釋出者/訂閱者模式(publisher/subscriber pattern)就是滿足這種需求,設計模式中也叫觀察者模式。釋出者儲存一個方法集合,並且提供一個註冊方法,讓訂閱者把自己的方法註冊進去,這樣在事件發生的時候,釋出者可以呼叫註冊到儲存集合中的所有方法。

有以下要點:

  • 釋出者(publisher)
  • 訂閱者(subscriber)
  • 事件處理程式(event handler)訂閱者註冊到釋出者的方法
  • 觸發(raise)事件 當事件被呼叫(invoke)或觸發(fire)時,所有註冊的事件處理程式會被依次呼叫

使用步驟

  1. 宣告委託型別
  2. 宣告事件
  3. 事件處理程式宣告
  4. 訂閱事件/註冊事件
  5. 觸發事件(呼叫事件)

程式碼示例:

using EventDemo;

void Main()
{
	Publisher pub = new Publisher(); //釋出者物件
	Subscriber sub = new Subscriber(); //訂閱者物件
	pub.BtnPressEvent += sub.ProcBtnPress; //4、訂閱事件(或者說給事件新增處理程式)
	pub.ButtonPress(); //5、觸發事件
}

namespace EventDemo
{
	//1、宣告委託型別
	delegate void DelButton(object sender, int id);
	
	//釋出者型別
	class Publisher
	{
		//2、宣告一個事件成員變數
		public event DelButton BtnPressEvent;
		
		//事件觸發方法
		public void ButtonPress()
		{
			Button button1 = new Button();
            if (BtnPressEvent != null)
            {
                BtnPressEvent(button1, 10);
            }
		}
	}
	
	//訂閱者型別
	class Subscriber
	{
		//3、事件處理程式(按鈕按下處理)
		public void ProcBtnPress(object sender, int id)
		{
			Console.WriteLine($"Id of button is {id}");
		}
	}
}//namespace

1、宣告委託型別

//1、宣告委託型別
delegate void DelButton(object sender, int id);

事件的宣告需要依賴此委託型別,並且事件處理程式的格式要與此委託保持一致。事件與委託很像,它包含了一個私有的內部委託。

  • 事件的委託是私有的,無法被直接訪問
  • 事件提供的操作比委託少,一般只有新增、刪除和呼叫事件處理程式
  • 事件觸發時,它通過呼叫內部的委託,來依次呼叫存放在委託方法列表中的事件處理程式

2、宣告事件變數

語法格式:

public event 委託型別 事件名稱;

//2、宣告一個事件成員變數,並且被隱式自動初始化為null
public event DelButton BtnPressEvent;

public static event DelButton BtnPressEvent; //宣告為靜態成員,區域性於類

public event DelButton Event1, Event2, Event3; //同時宣告多個事件

注意,事件是一個成員變數,而不是型別,它可以宣告在類和結構中。這裡使用public修飾後,外部的程式才可以向該事件註冊處理程式。

BCL中有一個EventHandler的委託,專門用於系統事件。

3、事件處理程式

在訂閱者型別中,定義了事件處理程式,該方法的格式必須保持和委託型別一致,要有相同的返回值和方法簽名。該事件處理程式用於在事件觸發時,被事件呼叫。最典型的就是響應winform或wpf中控制元件的事件,譬如像按鈕按下、下拉框選擇更改、文字框內容修改的事件等等。

//訂閱者型別
class Subscriber
{
    //3、事件處理程式(按鈕按下處理)
    public void ProcBtnPress(object sender, int id)
    {
        Console.WriteLine($"Id of button is {id}");
    }
}

4、訂閱事件

有了事件處理程式後,就可以把我們新增的方法註冊到事件上了,或者說訂閱該事件。在示例中,我們把自定義的事件處理方法ProcBtnPress通過+=的方式,新增到事件中去,這和委託增加方法的方式一致。

這樣一來,可以把多個訂閱者物件的自定義方法新增到事件中,又叫做多個訂閱者訂閱了同一個釋出者的事件。

void Main()
{
	Publisher pub = new Publisher(); //釋出者物件
	Subscriber sub = new Subscriber(); //訂閱者物件
	pub.BtnPressEvent += sub.ProcBtnPress; //4、訂閱事件(或者說給事件新增處理程式)
	pub.ButtonPress(); //5、觸發事件
}

5、事件觸發

在示例中,我們在釋出者型別中,寫了一個事件觸發的方法。事件觸發其實就是在某個需要的地方,呼叫該事件,內部會依次執行委託中的方法。

//釋出者型別
class Publisher
{
    //2、宣告一個事件成員變數
    public event DelButton BtnPressEvent;

    //3、事件觸發方法
    public void ButtonPress()
    {
        Button button1 = new Button();
        if (BtnPressEvent != null)
        {
            BtnPressEvent(button1, 10);
        }
    }
}

然後在外部呼叫事件觸發方法。

void Main()
{
	Publisher pub = new Publisher(); //釋出者物件
	Subscriber sub = new Subscriber(); //訂閱者物件
	pub.BtnPressEvent += sub.ProcBtnPress; //4、訂閱事件(或者說給事件新增處理程式)
	pub.ButtonPress(); //5、觸發事件 
}

標準事件的使用

上面說到BCL中有一個EventHandler的委託,專門用於系統事件,它是.Net框架提供的標準模式。

//sender: 發起事件的物件的引用,型別是object可以相容所有型別,但是使用的時候需要把object轉成對應型別
//s: 觸發事件傳進來的引數基類引用
public delegate void EvnetHandler(object sender, EventArgs e);

第二個引數要注意, 型別EventArgs是引數基類型別, 並不能直接使用, 要傳遞引數必須要派生一個引數類繼承自EventArgs用來傳遞引數。

EventArgs類的定義:

// System.EventArgs
using System;
using System.Runtime.CompilerServices;

[Serializable]
[TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
public class EventArgs
{
	public static readonly EventArgs Empty = new EventArgs();
}

下面我們使用標準事件委託EventHandler和自定義引數類,來實現事件的使用

using EventDemo;

void Main()
{
	CustomButton btn = new CustomButton(); //釋出者物件
	Subscriber sub = new Subscriber(); //訂閱者物件
	btn.BtnPressEvent += sub.ProcBtnPress; //訂閱事件
	btn.Click(); //事件觸發
}

namespace EventDemo
{
	//釋出者類, 這裡寫了一個簡單的自定義按鈕類作為釋出者
	class CustomButton
	{
		//2、這裡我們使用系統的標準事件委託來宣告事件變數
		//這裡的格式是使用了泛型委託,將預設的EventArgs引數類替換成我們自定義的
		public event EventHandler<CustomButtonEventArgs> BtnPressEvent;

		//自定義引數變數
		private CustomButtonEventArgs buttonEventArgs = new CustomButtonEventArgs();
		
		//3、事件觸發方法
		public void Click()
		{
			buttonEventArgs.Id = 10;
			buttonEventArgs.Value = 255;
			
			if (BtnPressEvent != null)
			{
				BtnPressEvent(this, buttonEventArgs);
			}
		}
	}
	
	//訂閱者類
	class Subscriber
	{
		//4、自定義事件處理程式(按鈕按下處理)
		//注意,這裡的引數要和標準委託EventHandler保持一致
		public void ProcBtnPress(object sender, EventArgs e)
		{
			Console.WriteLine($"The publisher Type is {sender.GetType().Name}");

			CustomButtonEventArgs ea = e as CustomButtonEventArgs;
			Console.WriteLine($"The eventArgs is {ea.Id} and {ea.Value}");
		}
	}
	
	//1、自定義按鈕的引數類
	class CustomButtonEventArgs : EventArgs
	{
		//這裡的引數可以存放一些外部的自定義資料
		public int Id { get; set; }
		public int Value { get; set; }
	}
	
}//namespace