十三、事件
13.1 多播委托
13.1.1 基本概念
- 一个委托类型对象可以引用多个方法
- 每个方法可以来自不同的订阅者
- 通过委托,可以将单一事件的通知(如对象状态的改变),一次发布给多个订阅者
13.1.2 订阅者
- 需要根据状态变化执行相应的操作
- 订阅者类中已经实现了委托类型的方法
class Cooler
{
public Cooler(float temperature)
{
Temperature = temperature;
}
public float Temperature { get; set; }
public void OnTemperatureChanged(float newTemperature)
{
if(newTemperature > Temperature)
{
System.Console.WriteLine("Cooler: On");
}
else
{
System.Console.WriteLine("Cooler: Off");
}
}
}
13.1.3 发布者
- 状态变化的来源
- 声明委托类型Action<>并作为属性
- 状态发生变化就调用委托类型
public class Thermostat
{
public float CurrentTemperature
{
get { return _CurrentTemperature; }
set
{
if (value != CurrentTemperature)
{
_CurrentTemperature = value;
OnTemperatureChanged?.Invoke(value);
}
}
}
private float _CurrentTemperature { get; set; }
public Action<float> OnTemperatureChanged { get; set; }
}
13.1.4 订阅与发布的绑定
class Program
{
public static void Main()
{
Thermostat thermostat = new Thermostat();
Cooler cooler = new Cooler(80);
thermostat.OnTemperatureChanged += cooler.OnTemperatureChanged;
thermostat.CurrentTemperature = 90;
}
}
13.1.5 对委托类型对象进行null判定
- 在判断完成到调用委托之间订阅者可能移除订阅使委托对象重新变为null
- C#6.0,null条件操作符
- C#6.0之前,判null前用局部变量保存委托对象
- 虽然委托类型是引用类型
- 但移除时返回的是新对象而不是在原来的对象上进行改变
- 可以用局部变量进行保存
- 虽然能解决null值问题,但可能会调用一个已经取消订阅的订阅者的方法
13.1.6 顺序调用
13.1.7 多播委托的内部机制
- delegate是System.MulticastDelegate的别名
- 包含对象引用、方法引用
- 以及另一个MulticastDelegate对象的引用
13.1.8 错误处理
- 委托链中的一个委托方法可能会发生异常
- 通过委托链顺序执行委托方法时将会在发生异常时中断
- 信息将无法通知给委托链后面的订阅者
- 实例方法GetInvocationList()
- 作用
- 返回委托链中的所有委托方法
- 可以手动循环委托链中的所有方法,并在调用时进行异常处理
- 使用场景
13.2 事件
- 作用
- 解决多播委托中的缺陷
- 委托链可能被错误覆盖
- 在发布者外部可以直接调用委托对象
- 因为需要注册委托方法,所以委托对象为public
- 这样可以直接调用委托对象发布通知
- 在声明上和多播委托的区别
public class Thermostat
{
public float CurrentTemperature
{
get { return _CurrentTemperature; }
set
{
if (value != CurrentTemperature)
{
_CurrentTemperature = value;
OnTemperatureChanged?.Invoke(this,new TemperatureArgs(value));
}
}
}
private float _CurrentTemperature { get; set; }
public class TemperatureArgs : System.EventArgs
{
public TemperatureArgs(float newTemperature)
{
NewTemperature = newTemperature;
}
public float NewTemperature { get; set; }
}
public event EventHandler<TemperatureArgs> OnTemperatureChanged = delegate { };
}
13.2.1 event关键字
- 作用
- 封装订阅
- 封装发布
- 不是取代public,而是加在public后面增强封装
- event关键字不允许在发布者外部直接调用委托对象
- 正常应该是在setter方法中,在赋值完毕后执行委托
- 因为委托是用public修饰的,所以,可以在任何地方直接调用委托
13.2.2 EventHandler<TEventArgs>
- 通用委托类型
- 替代Action<>,是系统定义和声明的一种委托类型
- 委托类型作为字段而不是属性使用
- 由于event已经提供了封装,所以设为属性没有意义
- 属性的作用就是提供封装
- 空委托
- delegate { }
- 为委托字段赋值为空委托,可以不进行null检查
- public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e) where TEventArgs : EventArgs
- EventHandler<TEventArgs>委托类型的完整签名
- sender
- 表示发布者,若一个订阅者订阅了多个发布者提供的通知,可以此区分,静态通知为null
- TEventArgs
- 事件数据
- 必须是System.EventArgs的派生类,包含事件的附加信息以及Empty属性,Empty表示不存在事件数据
13.2.3 内部机制
- 编译器将event修饰的委托类型字段的访问修饰符由public改为private
- 创建与委托对应的属性,内部对应+=和-=的方法
- +=和-=的内部实现都是调用相应的委托静态方法:
- System.Delegate.Combine()
- System.Delegate.Remove()
- 与编译器生成的CIL代码对应的C#代码
public class Thermostat
{
private EventHandler<TemperatureArgs> _OnTemperatureChanged;
public void add_OnTemperatureChanged(EventHandler<TemperatureArgs> handler)
{
System.Delegate.Combine(_OnTemperatureChanged, handler);
}
public void remove_OnTemperatureChanged (EventHandler<TemperatureArgs> handler)
{
System.Delegate.Remove(_OnTemperatureChanged, handler);
}
public event EventHandler<TemperatureArgs> OnTemperatureChanged
{
add
{
add_OnTemperatureChanged(value);
}
remove
{
remove_OnTemperatureChanged(value);
}
}
}
13.2.4 System.EventArgs的派生类
- 事件数据必须是System.EventArgs的派生类,所以发布者中必须声明一个事件数据类
- 虽然订阅者可以通过sender获取当前事件数据
- 但是在调用委托和订阅者执行方法之间,事件数据可能再次变化
- 所以在调用委托时将新事件数据一同传递
- System.EventArgs
13.2.5 自定义委托类型
- 通用委托
- 泛型
- 不同的EventArgs派生类可以不需要重新定义委托类型
- 自定义委托类型
- 非泛型
- 由于接收参数不同,不同的EventArgs派生类需要重新定义委托类型
public class Thermostat
{
public float CurrentTemperature
{
get { return _CurrentTemperature; }
set
{
if (value != CurrentTemperature)
{
_CurrentTemperature = value;
OnTemperatureChanged?.Invoke(this,new TemperatureArgs(value));
}
}
}
private float _CurrentTemperature { get; set; }
public class TemperatureArgs : System.EventArgs
{
public TemperatureArgs(float newTemperature)
{
NewTemperature = newTemperature;
}
public float NewTemperature { get; set; }
}
public delegate void TemperatureChangeHandler(object sender, TemperatureArgs newTemperature);
public event TemperatureHandler OnTemperatureChanged;
}
13.2.6 自定义事件
- 自定义add、remove处理程序
- 用event关键字封装订阅,protect封装发布
public class Thermostat
{
public float CurrentTemperature
{
get { return _CurrentTemperature; }
set
{
if (value != CurrentTemperature)
{
_CurrentTemperature = value;
OnTemperatureChanged?.Invoke(this,new TemperatureArgs(value));
}
}
}
private float _CurrentTemperature { get; set; }
public class TemperatureArgs : System.EventArgs
{
public TemperatureArgs(float newTemperature)
{
NewTemperature = newTemperature;
}
public float NewTemperature { get; set; }
}
public event EventHandler<TemperatureArgs> OnTemperatureChanged
{
add
{
System.Delegate.Combine(value, _OnTemperatureChanged);
}
remove
{
System.Delegate.Remove(_OnTemperatureChanged, value);
}
}
protected EventHandler<TemperatureArgs> _OnTemperatureChanged;
}