C#复习笔记-事件
使用委托时,一般会出现两种角色,广播者和订阅者。广播者是包含委托字段的类型,它通过委托决定何时进行广播。订阅者是方法的接收者。它通过在广播者的委托上调用+=或者-=来决定何时开始监听何时结束监听。事件是一种使用委托的功能实现广播者或订阅者模型的结构。使用委托的主要目的是保证订阅者之间互不影响。声明事件的最简单的方法是在委托成员前面加上event关键字:
//定义一个委托,接受2个decimal类型的参数 public delegate void PirceChangedHandler(decimal oldPirce, decimal newPrice); public class Broadcaster { //定一个PirceChanged事件 public event PirceChangedHandler PriceChanged; decimal _price; public decimal Price { get { return _price; } set { if (_price == value) return; decimal oldprice = _price; _price = value; if (PriceChanged != null) PriceChanged(oldprice, _price); } } }
EventArgs是为事件传递信息的基类。我们可以继承EventArgs以便于在事件触发的时候传递值参数。EventArgs子类应当根据它包含的信息来命名,而非根据使用它的事件命名。一般将数据以属性或制度字段的方式暴露给外界。
public class PriceChangedEventArgs : EventArgs { public readonly decimal Price; public readonly decimal OldPrice; public PriceChangedEventArgs(decimal price, decimal oldPrice) { Price = price; OldPrice = oldPrice; } }
定义了EvaenArgs子类以后,下一步就要定义事件的委托了,委托必须满足下面三个条件:
- 委托必须以void作为返回值。
- 委托必须接受两个参数。第一个参数是object类型,第二个参数是EventArgs子类。第一个参数表明了时间的广播者,第二个参数包含了需要传递的信息。
- 委托的名称必须以EventHandler结尾。如下:
public delegate void PriceChangedEventHandler(object sender, PriceChangedEventArgs e);
框架定义了一个名为System.EventHandler<>的泛型委托也满足以上三个条件,事件模式还需要编写一个protected的虚方法来触发事件,方法名和时间名一致,以On为前缀,接收唯一的EvenArgs参数:
public class Stock { public event EventHandler<PriceChangedEventArgs> PriceChanged; protected virtual void OnPriceChanged(PriceChangedEventArgs e) { var temp = PriceChanged;//为了多线程下可靠的工作,在测试和调用委托之前需要将他保存到零时变量 if (temp != null) temp(this, e); PriceChanged?.Invoke(this, e);//也可以使用null条件运算符来避免声明临时变量,这种写法是最好的事件触发方式 } }
完整的代码如下:
public class PriceChangedEventArgs : System.EventArgs { public readonly decimal LastPrice; public readonly decimal NewPrice; public PriceChangedEventArgs(decimal lastPrice, decimal newPrice) { LastPrice = lastPrice; NewPrice = newPrice; } } public class Stock { public event EventHandler<PriceChangedEventArgs> PriceChanged; protected virtual void OnPriceChanged(PriceChangedEventArgs e) { //为了多线程下可靠的工作,调用委托之前需要将他保存到零时变量 //var temp = PriceChanged; //if (temp != null) // temp(this, e); PriceChanged?.Invoke(this, e);//也可以这么写,线程安全又书写简单,是最好的触发方式。 } decimal price; public decimal Price { get { return price; } set { if (price == value) return; decimal priceold = price; price = value; OnPriceChanged(new PriceChangedEventArgs(priceold, price)); } } } using App; Stock stock = new Stock(); stock.Price = 20; stock.PriceChanged += Stock_PriceChanged; for (int i = 0; i < 10; i++) { stock.Price = i + 20; Task.Delay(1000).Wait(); } void Stock_PriceChanged(object? sender, PriceChangedEventArgs e) { Console.WriteLine($"当前价格:{e.NewPrice},上一次的价格{e.LastPrice}"); } Console.ReadKey();
和方法一样,事件也可以重写,可以是虚的,抽象的密封的,静态的。
从别后, 忆相逢, 几会魂梦与汝同。