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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#本质论(第3版) 详细介绍C# 4.0 第1章 c#概述 1.1 hello world 1.2 c#语法基础 1.2.1 c#关键字 1.2.2 类型定义 1.2.3 main 1.2.4 语句和语句分隔符 1.2.5 空白 1.3 使用变量 1.3.1 数据类型 1.3.2 变量的声明 1.3.3 变量的赋值 1.3.4 变量的使用 1.4 控制台输入和输出 1.4.1 从控制台获取输入 1.4.2 将输出写入控制台 1.5 注释 1.6 托管执行和公共语言基础结构 1.7 c#和net版本 .1.8 cil和ildasm 1.9 小结 第2章 数据类型 2.1 基本数值类型 2.1.1 整数类型 2.1.2 浮点类型 2.1.3 decimal类型 2.1.4 字面值 2.2 更多基本类型 2.2.1 布尔类型 2.2.2 字符类型 2.2.3 字符串 2.3 null和void 2.3.1 null 2.3.2 void 2.4 类型的分类 2.4.1 值类型 2.4.2 引用类型 2.5 可空修饰符 2.6 数据类型之间的转换 2.6.1 显式转型 2.6.2 隐式转型 2.6.3 不进行转型的类型转换 2.7 数组 2.7.1 数组的声明 2.7.2 数组的实例化和赋值 2.7.3 数组的使用 2.7.4 字符串作为数组使用 2.7.5 常见错误 2.8 小结 第3章 运算符和控制流 3.1 运算符 3.1.1 一元运算符正和负 3.1.2 二元算术运算符 3.1.3 圆括号运算符 3.1.4 赋值运算符 3.1.5 递增和递减运算符 3.1.6 常量表达式 3.2 流控制概述 3.2.1 if语句 3.2.2 嵌套if 3.3 代码块 3.4 作用域和声明空间 3.5 布尔表达式 3.5.1 关系运算符和相等性运算符 3.5.2 逻辑布尔运算符 3.5.3 逻辑求反运算符 3.5.4 条件运算符 3.5.5 空接合运算符 3.6 按位运算符 3.6.1 移位运算符 3.6.2 按位运算符 3.6.3 按位赋值运算符 3.6.4 按位取反运算符 3.7 控制流语句 3.7.1 whi.1 e和do/while循环 3.7.2 for循环 3.7.3 foreach循环 3.7.4 switch语句 3.8 跳转语句 3.8.1 break语句 3.8.2 continue语句 3.8.3 go to语句 3.9 c#预处理器指令 3.9.1 排除和包含代码 3.9.2 定义预处理器符号 3.9.3 生成错误和警告 3.9.4 关闭警告消息 3.9.5 nowarn:选项 3.9.6 指定行号 3.9.7 可视编辑器提示 3.10 小结 第4章 方法和参数 4.1 方法的调用 4.1.1 命名空间 4.1.2 类型名称 4.1.3 作用域 4.1.4 方法名称 4.1.5 参数 4.1.6 方法返回值 4.1.7 语句与方法调用的比较 4.2 方法的声明 4.2.1 参数声明 4.2.2 方法返回值声明 4.3 uslng指令 4.4 main()的返回值和参数 4.5 参数 4.5.1 值参数 4.5.2 引用参数 4.5.3 输出参数 4.5.4 参数数组 4.6 递归 4.7 方法重载 4.8 可选参数 4.9 用异常实现基本错误处理 4.9.1 捕捉错误 4.9.2 使用throw语句报告错误 4.10 小结 第5章 类 5.1 类的定义和实例化 5.2 实例字段 5.2.1 实例字段的声明 5.2.2 实例字段的访问 5.3 实例方法 5.4 使用this关键字 5.5 访问修饰符 5.6 属性 5.6.1 属性的声明 5.6.2 自动实现的属性 5.6.3 命名规范 5.6.4 提供属性验证 5.6.5 读和只写属性 5.6.6 为取值方法和赋值方法指定访问修饰符 5.6.7 属性作为虚字段使用 5.6.8 属性和方法调用不允许作为ref或out参数值使用 5.7 构造器 5.7.1 构造器的声明 5.7.2 默认构造器 5.7.3 对象初始化器 5.7.4 构造器的重载 5.7.5 使用this调用另一个构造器 5.8 静态成员 5.8.1 静态字段 5.8.2 静态方法 5.8.3 静态构造器 5.8.4 静态属性 5.8.5 静态类 5.9 扩展方法 5.10 封装数据 5.10.1

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值