C#委托、匿名方法、事件

委托

  1. 委托概述
    • 委托是持有一个或多个方法的对象
      • 可以理解成一个类型安全的、面向对象的函数指针
    • 委托可以包含多个方法,调用委托时,包含的每一个方法都会执行
    • 可以把delegate看做一个包含有序方法列表的对象,这些方法具有相同的签名和返回类型
      • 方法的列表被称为调用列表
      • 委托保存的方法可以来自任何类或者结构,可以是静态方法也可以是实例方法,只要它们在委托的返回类型以及委托的签名(包括refout修饰符)
  2. 声明委托类型
    • 不一定需要在类内部声明,因为它是类型的声明
  3. 创建委托对象
    • 两种方式
      • 可以使用new去创建委托,构造函数的参数即为方法名(注意不带括号
      • 可以使用快捷语法,在方法名称和其对应二点委托类型之间存在隐式转换,所以可以之间将方法名赋值给委托变量
    • 除了为委托分配内存,创建委托对象还会把第一个方法放入委托的调用列表
  4. 组合委托
    • 委托可以使用额外的运算符(+)来组合,这个运算最终会创建一个新委托,其调用列表连接了作为操作数的两个委托的调用列表副本
    • 组合委托没有修改作为操作数的委托,委托是恒定的,委托对象被创建以后不能在被改变,委托的赋值是引用的赋值,让原来的引用指向了新的对象
  5. 为委托添加方法
    • 委托虽然是不变的,但是C#提供了看上去可以为委托添加方法的语句,即使用+=运算符
    • +=运算符后面可以加相同签名和返回类型的方法或者委托
    • 使用+=时,仍然没有修改委托,同样是创建了一个新的委托
    • 可以为委托添加多个方法,每次添加都会在调用列表创建一个新的元素。
  6. 从委托移除方法
    • 可以使用-=从委托移除方法,同样是创建了一个新的委托
    • 如果要移除的方法在调用列表里有多个实例,-=运算符会移除最后一个实例
    • 如果委托中不存在要移除的方法,则没有效果
    • 如果调用的委托为null,则会抛出异常
    • 如果委托的调用列表为空,即为null,所以把所有方法移除后,委托会变成null
  7. 调用委托
    • 可以像调用方法一样调用委托
    • 调用委托的参数将会用于委托中的所有方法
    • 调用委托时中只会得到最后一个方法的返回值,其他的返回值均会被忽略,包括out修饰的返回值参数
    • 如果委托中同一个方法出现多次,则每一次都会被调用
    • 建议在调用委托前判断是否为空,或者使用另一种方式del?.Invoke()
  8. 调用带引用参数的委托
    • 如果委托有引用参数,参数值会根据调用列表中的一个或多个方法的返回值而改变
    • 在调用委托列表的下一个方法时,参数的新值会传给下一个方法

匿名方法

  1. 匿名方法概述
    • 如果方法只用来初始化委托,没有必要创建独立的具名方法,可以使用匿名方法
    • 匿名方法是在初始化委托时内联声明的方法
  2. 使用匿名方法
    • 声明委托时作为初始化表达式
    • 组合委托时在赋值语句右边
    • 为委托增加事件时在赋值语句右边
  3. 匿名方法的语法
    • delegate关键字
    • 参数列表,括号中包括参数类型和参数名,没有参数可以省略(圆括号也可以省略)
    • 语句块,包含了匿名方法的代码
    • 返回值
      • 不需要显式写出返回类型,内部的返回值类型需要与委托匹配
    • 参数
      • 除了数组参数,匿名方法的参数列表必须在以下三方面与委托匹配
        • 参数数量
        • 参数类型及位置
        • 修饰符
      • 可以通过使圆括号为空或省略圆括号来简化匿名方法的参数列表,但是必须满足:
        • 委托的参数列表不能包含任何out参数
        • 匿名方法不使用任何参数
    • params参数
      • 委托类型声明指定最后一个参数为params类型的参数
      • 匿名方法参数列表忽略了params关键字
  4. 变量和参数的作用域
    • 参数和方法内部变量的作用域被限制在匿名方法内
    • 外部变量
      • 外围作用域的变量叫做外部变量
      • 用在匿名方法实现代码中的外部变量称为被方法捕获
    • 捕获变量的生命周期的扩展
      • 只要捕获方法还是委托的一部分,即使变量离开了作用域,捕获的外部变量也会一直有效
  5. Lambda表达式
    • C#3.0引入的匿名方法的简化方式
      • 删除delegate关键字
      • 在参数列表和匿名方法主体之间
    • 进一步简化
      • 省略参数的类型,只留下参数名
        • 省略类型的参数列表称为隐式类型
        • 带有参数的参数列表称为显式类型
        • 要么都是显式类型,要么都是隐式类型
      • 如果只有一个参数并且是隐式类型,可以省略外围的圆括号
      • 如果没有返回值类型且只有一条语句,可以省略花括号
      • 如果方法体只有一句返回语句,可以省略花括号和return语句
    • 参数列表的要点
      • 参数列表必须匹配
      • 参数列表中的参数不一定需要包含类型(即隐式类型),除非委托有ref或out参数修饰,此时必须注明类型(即显式类型)
      • 只有当只有一个参数且为隐式类型时,圆括号才能省略,否则必须有括号
      • 如果没有参数,必须使用一组空的圆括号

事件

  1. 发布者/订阅者模式
    • 发布者定义了一系列程序的其他部分可能感兴趣的事件
    • 其他类可以注册,以便这些事件发生时发布者可以通知它们,这些订阅者类通过向发布者提供一个方法来注册以获取通知
    • 当事件发生时,发布者触发事件,然后执行订阅者提交的所有事件
    • 由订阅者提供的方法称为回调方法,因为发布者通过执行这些方法来往回调用订阅者的方法。还可以将它们称为事件处理程序,因为它们是为处理事件而调用的代码
  2. 事件概述
    • 重要事项:
      • 订阅者 publisher:发布某个事件的类或结构,其他类可以在该事件发生时得到通知
      • 发布者 subscriber:注册并在事件发生时得到通知的类或结构
      • 事件处理程序 event handler:由订阅者注册到事件的方法,在发布者触发事件时执行。事件处理程序方法可以定义在事件所在的类或结构中,也可以定义在不同的类或结构中
      • 触发事件 raise:调用(invoke)或触发(fire)事件的术语。在事件触发时,所有注册到它的方法都会被依次调用
    • 实际上,事件就像是专门用于某种特殊用途的委托。事件包含了一个私有的委托
      • 事件提供了对它的私有的控制委托的结构化访问,也就是说无法直接访问委托
      • 事件中可用的操作比委托要少,只可以添加、删除或调用事件处理程序
      • 事件被触发时,它调用委托来依次调用调用列表中的方法
  3. 声明事件
    • 发布者必须提供事件对象。创建事件比较简单,只需要委托类型和名字。
    • 声明事件的语法如下:
      • 事件声明在一个类中
      • 他需要委托类型的名称,任何附加到事件的处理程序都必须与委托类型的签名和返回类型匹配
      • 它声明为public,这样其他类和结构可以在它上面注册事件处理程序
      • 不能使用new来创建它的对象
      • public event EventHandler eventHandler;
      • 其中event为关键字,EventHandler为委托类型,eventHandler为事件名
      • 也可以生成为static
    • 事件是成员,不是类型
      • 和字段、方法一样,事件是类或结构的成员
      • 不能在一段可执行代码中声明事件
      • 他必须声明在类或结构中
      • 被隐式自动初始化为null
    • BCL声明了一个叫做EventHandler的委托,专门用于系统事件
  4. 订阅事件
    • 使用+=运算符来为事件增加事件处理程序,事件处理程序位于运算符右边
    • 事件处理程序的规范可以是实例方法名、静态方法名、匿名方法、Lambda表达式
  5. 标准事件的用法
    • 程序事件的异步处理是C#事件的绝佳场景(如GUI编程中按钮点击、按下按键或系统定时器)
    • .NET框架提供了一个标准模式,即System命名空间声明的EventHandler委托类型
    • 该类型的声明如下:public delegate void EventHandler(object sender, EventArgs e);
      • 第一个参数用来保存触发事件的对象的引用
      • 第二个参数用来保存状态信息
      • 返回类型是void
    • 关于第二个参数EventArgs
      • 该参数不用于保存数据,它用于不需要传递数据的事件处理程序
      • 如果希望传递数据,必须声明一个派生自EventArgs的类,用合适的字段保存需要传递的数据
      • 目的是让EventArgs提供一个对所有事件和事件处理器都通用的签名
    • 一般传递参数
      • 第一个参数可以使用this,第二个参数如果不传递数据可以使用null
      • 如果要传递数据,需要使用EventArgs的派生类,并有对应版本的泛型委托,泛型的类型参数即为该派生类
  6. 移除事件处理程序
    • 可以使用-=移除事件
    • 如果注册了多次,则只移除列表中的最后一个实例
  7. 事件访问器
    • 可以通过为事件定义事件访问器改变+=和-=两个运算符的操作
    • 两个访问器,add对应+=,remove对应-=
    • 声明方式类似于属性
    • 两个访问器都有value隐式值参数
    • 如果使用访问器,则不包含内部委托对象,需要自己实现委托来进行事件注册,类似属性给出get和set的实现时需要手动绑定字段
    • 两个访问器表现为void方法,不能使用包含返回值的return