C# 6.0本质论(事件)

十三、事件

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条件操作符
      • 特殊之处在于保证判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关键字

  • 作用
    • 封装订阅
      • event关键字只允许外部通过+=、-=进行订阅
    • 封装发布
      • 不是取代public,而是加在public后面增强封装
      • event关键字不允许在发布者外部直接调用委托对象
        • 正常应该是在setter方法中,在赋值完毕后执行委托
        • 因为委托是用public修饰的,所以,可以在任何地方直接调用委托

13.2.2 EventHandler<TEventArgs>

  • 通用委托类型
    • 替代Action<>,是系统定义和声明的一种委托类型
  • 委托类型作为字段而不是属性使用
    • 由于event已经提供了封装,所以设为属性没有意义
    • 属性的作用就是提供封装
  • 空委托
    • delegate { }
    • 为委托字段赋值为空委托,可以不进行null检查
      • 只要发布者中不存在将委托置为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
    • 具有Empty属性
      • 可以指出不存在事件数据

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; }
	}
	//EventHandler<TEventArgs>省略了该行定义
	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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值