1. 委托

**(注:此章非常重要,特别是对于图形界面相关的区别于MFC和QT等的消息机制,委托是基石。)

委托是用来处理其他语言(如 C++、Pascal 和 Modula)需用函数指针来处理的情况的。不过与 C++ 函数指针不同,委托是完全面向对象的;另外,C++ 指针仅指向成员函数,而委托同时封装了对象实例和方法。

委托声明定义一个从 System.Delegate 类派生的类。委托实例封装了一个调用列表,该列表列出了一个或多个方法,每个方法称为一个可调用实体。对于实例方法,可调用实体由该方法和一个相关联的实例组成。对于静态方法,可调用实体仅由一个方法组成。用一个适当的参数集来调用一个委托实例,就是用此给定的参数集来调用该委托实例的每个可调用实体。

委托实例的一个有趣且有用的属性是:它不知道也不关心它所封装的方法所属的类;它所关心的仅限于这些方法必须与委托的类型兼容(第 15.1 节)。这使委托非常适合于“匿名”调用。

1.1 委托声明

delegate-declaration 是一种 type-declaration(第 9.6 节),它声明一个新的委托类型。

delegate-declaration:
attributesopt   delegate-modifiersopt   delegate   return-type  
          identifier  variant-type-parameter-listopt  
          (   formal-parameter-listopt   )   type-parameter-constraints-clausesopt   ;

delegate-modifiers:
delegate-modifier
delegate-modifiers   delegate-modifier

delegate-modifier:
new
public
protected
internal
private

同一修饰符在一个委托声明中多次出现属于编译时错误。

new 修饰符仅允许在其他类型中声明的委托上使用,在这种情况下该修饰符表示所声明的委托会隐藏具有相同名称的继承成员,详见第 10.3.4 节。

public、protected、internal 和 private 修饰符将控制委托类型的可访问性。根据委托声明所在的上下文,可能不允许使用其中某些修饰符(第 3.5.1 节)。

上述的语法产生式中,identifier 用于指定委托的类型名称。

可选的 formal-parameter-list用于指定委托的参数,而 return-type 则指定委托的返回类型。

可选的 variant-type-parameter-list(第 13.1.3 节)指定委托本身的类型形参。

委托类型的返回类型必须为 void 或输出安全(第 13.1.3.1 节)。

委托类型的所有形参类型都必须是输入安全的。此外,所有 out 或 ref 参数类型也必须是输出安全的。请注意,由于基础执行平台的限制,甚至 out 形参也必须是输入安全的。

C# 中的委托类型是名称等效的,而不是结构等效的。具体地说,对于两个委托类型,即使它们具有相同的参数列表和返回类型,仍被认为是不同的两个委托类型。但是,两个不同但结构上等效的委托类型的实例可能比较为相等(第 7.9.8 节)。

例如:

delegate int D1(int i, double d);

class A
{
public static int M1(int a, double b) {...}
}

class B
{
delegate int D2(int c, double d);

public static int M1(int f, double g) {...}

public static void M2(int k, double l) {...}

public static int M3(int g) {...}

public static void M4(int g) {...}
}

方法 A.M1 和 B.M1  与委托类型 D1 和 D2 都兼容,因为这两个方法的返回类型和参数列表相同;但是,这两个委托类型是两个不同的类型,因此这两个委托类型不可互换。方法 B.M2 和 B.M3、B.M4 与委托类型 D1 和 D2 不兼容,因为这两个方法的返回类型或参数列表不同。

与其他泛型类型声明一样,必须提供类型实参才能创建构造委托类型。构造委托类型的形参类型和返回类型是通过将委托声明中的每个类型形参替换为构造委托类型的对应类型实参来创建的。结果返回类型和形参类型用于确定哪些方法与构造委托类型兼容。例如:

delegate bool Predicate<T>(T value);

class X
{
static bool F(int i) {...}

static bool G(string s) {...}
}

方法 X.F 与委托类型 Predicate<int> 兼容,方法 X.G 与委托类型 Predicate<string> 兼容。

声明一个委托类型的唯一方法是通过 delegate-declaration。委托类型是从 System.Delegate 派生的类类型。委托类型隐含为 sealed,所以不允许从一个委托类型派生任何类型。也不允许从 System.Delegate 派生非委托类类型。请注意:System.Delegate 本身不是委托类型;它是从中派生所有委托类型的类类型。

C# 提供了专门的语法用于委托类型的实例化和调用。除实例化外,所有可以应用于类或类实例的操作也可以相应地应用于委托类或委托实例。具体而言,可以通过通常的成员访问语法访问 System.Delegate 类型的成员。

委托实例所封装的方法集合称为调用列表。从某个方法创建一个委托实例时(第 15.2 节),该委托实例将封装此方法,此时,它的调用列表只包含一个“入口点”。但是,当组合两个非空委托实例时,它们的调用列表将连接在一起(按照左操作数在前、右操作数在后的顺序)以组成一个新的调用列表,其中包含两个或更多个“入口点”。

委托是使用二元 +(第 7.8.4 节)和 += 运算符(第 7.17.2 节)进行组合的。可以使用二元 -(第 7.8.5 节)和 -= 运算符(第 7.17.2 节)将一个委托从委托组合中移除。委托间还可以进行比较以确定它们是否相等(第 7.10.8 节)。

下面的示例演示多个委托的实例化及其相应的调用列表:

delegate void D(int x);

class C
{
public static void M1(int i) {...}

public static void M2(int i) {...}

}

class Test
{
static void Main() {
     D cd1 = new D(C.M1);     // M1
     D cd2 = new D(C.M2);     // M2
     D cd3 = cd1 + cd2;       // M1 + M2
     D cd4 = cd3 + cd1;       // M1 + M2 + M1
     D cd5 = cd4 + cd3;       // M1 + M2 + M1 + M1 + M2
}

}

实例化 cd1 和 cd2 时,它们分别封装一个方法。实例化 cd3 时,它的调用列表有两个方法 M1 和 M2,而且顺序与此相同。cd4 的调用列表中依次包含 M1、M2 和 M1。最后,cd5 的调用列表中依次包含 M1、M2、M1、M1 和 M2。有关组合(以及移除)委托的更多示例,请参见第 15.4 节。

1.2 委托兼容性

方法或委托 M 可兼容 (compatible)委托类型 D,前提是以下所有条件都成立:

  • D 和 M 具有相同数量的形参,并且 D 中的每个形参都具有与 M 中对应形参相同的 ref 或 out 修饰符。
  • 对于每个值形参(没有 ref 或 out 修饰符的形参),存在从 D 中形参类型到 M 中对应形参类型的标识转换(第 6.1.1 节)或隐式引用转换(第 6.1.6 节)。
  • 对于每个 ref 或 out 参数,D 中的参数类型与 M 中的参数类型相同。
  • 存在从 M 的返回类型到 D 的返回类型的标识或隐式引用转换。

1.3 委托实例化

委托的实例通过 delegate-creation-expression(第 7.6.10.5 节)或到委托类型的转换进行创建。因此,新创建的委托实例将引用以下各项之一:

  • delegate-creation-expression 中引用的静态方法,或者
  • delegate-creation-expression 中引用的目标对象(此对象不能为 null)和实例方法,或者
  • 另一个委托。

例如:

delegate void D(int x);

class C
{
public static void M1(int i) {...}
public void M2(int i) {...}
}

class Test
{
static void Main() {
     D cd1 = new D(C.M1);     // static method
     C t = new C();
     D cd2 = new D(t.M2);     // instance method
     D cd3 = new D(cd2);      // another delegate
}
}

委托实例一旦被实例化,它将始终引用同一目标对象和方法。记住,当组合两个委托或者从一个委托移除另一个时,将产生一个新的委托,该委托具有它自己的调用列表;被组合或移除的委托的调用列表将保持不变。

1.4 委托调用

C# 为调用委托提供了专门的语法。当调用非空的、调用列表仅包含一个入口点的委托实例时,它调用调用列表中的方法,委托调用所使用的参数和返回的值均与该方法的对应项相同。(有关委托调用的详细信息,请参见第 7.6.5.3 节。)如果在对这样的委托进行调用期间发生异常,而且没有在被调用的方法内捕捉到该异常,则会在调用该委托的方法内继续搜索与该异常对应的 catch 子句,就像调用该委托的方法直接调用了该委托所引用的方法一样。

如果一个委托实例的调用列表包含多个入口点,那么调用这样的委托实例就是按顺序同步地调用调用列表中所列的各个方法。以这种方式调用的每个方法都使用相同的参数集,即提供给委托实例的参数集。如果这样的委托调用包含引用参数(第 10.6.1.2 节),那么每个方法调用都将使用对同一变量的引用;这样,若调用列表中有某个方法对该变量进行了更改,则调用列表中排在该方法之后的所有方法都会见到此变更。如果委托调用包含输出参数或一个返回值,则它们的最终值就是调用列表中最后一个方法调用所产生的结果。

如果在处理此类委托的调用期间发生异常,而且没有在正被调用的方法内捕捉到该异常,则会在调用该委托的方法内继续搜索与该异常对应的 catch 子句,此时,调用列表中排在后面的任何方法将不会被调用。

试图调用其值为 null 的委托实例将导致 System.NullReferenceException 类型的异常。

下面的示例演示如何实例化、组合、移除和调用委托:

using System;

delegate void D(int x);

class C
{
public static void M1(int i) {
     Console.WriteLine("C.M1: " + i);
}

public static void M2(int i) {
     Console.WriteLine("C.M2: " + i);
}

public void M3(int i) {
     Console.WriteLine("C.M3: " + i);
}
}

class Test
{
static void Main() {
     D cd1 = new D(C.M1);
     cd1(-1);             // call M1

D cd2 = new D(C.M2);
     cd2(-2);             // call M2

D cd3 = cd1 + cd2;
     cd3(10);             // call M1 then M2

cd3 += cd1;
     cd3(20);             // call M1, M2, then M1

C c = new C();
     D cd4 = new D(c.M3);
     cd3 += cd4;
     cd3(30);             // call M1, M2, M1, then M3

cd3 -= cd1;          // remove last M1
     cd3(40);             // call M1, M2, then M3

cd3 -= cd4;
     cd3(50);             // call M1 then M2

cd3 -= cd2;
     cd3(60);             // call M1

cd3 -= cd2;          // impossible removal is benign
     cd3(60);             // call M1

cd3 -= cd1;          // invocation list is empty so cd3 is null

//     cd3(70);      // System.NullReferenceException thrown

cd3 -= cd1;          // impossible removal is benign
}
}

如语句 cd3 += cd1; 中所演示,委托可以多次出现在一个调用列表中。这种情况下,它每出现一次,就会被调用一次。在这样的调用列表中,当移除委托时,实际上移除的是调用列表中最后出现的那个委托实例。

就在执行最后一条语句 cd3 -= cd1; 之前,委托 cd3 引用了一个空的调用列表。试图从空的列表中移除委托(或者从非空列表中移除表中没有的委托)不算是错误。

产生的输出为:

C.M1: -1
C.M2: -2
C.M1: 10
C.M2: 10
C.M1: 20
C.M2: 20
C.M1: 20
C.M1: 30
C.M2: 30
C.M1: 30
C.M3: 30
C.M1: 40
C.M2: 40
C.M3: 40
C.M1: 50
C.M2: 50
C.M1: 60
C.M1: 60

转载于:https://www.cnblogs.com/yuyuanfeng/p/5609371.html

C# 语言规范_版本5.0 (第15章 委托)相关推荐

  1. C# 语言规范_版本5.0 (第10章 类)

    1. 类 类是一种数据结构,它可以包含数据成员(常量和字段).函数成员(方法.属性.事件.索引器.运算符.实例构造函数.静态构造函数和析构函数)以及嵌套类型.类类型支持继承,继承是一种机制,它使派生类 ...

  2. java语言程序设计基础篇14.6答案_《Java语言程序设计-基础篇》答案-第15章

    第15章 异常和断言 复习题 15.1 答:略 15.2 答:声明异常是为了在方法中产生异常时,以便通知方法的调用者. 在方法声明的头部使用关键字throws 声明,一个方法可以声明多个异常.如: p ...

  3. 【Android】15.0 第15章 广播和通知—本章示例主界面

    分类:C#.Android.VS2015: 创建日期:2016-02-28 一.简介 广播(Broadcast):其功能类似于收音机的广播,你只要调到那个台(只要在接收的类中注册了要接收的广播),就能 ...

  4. 第15章_存储过程与函数(创建存储过程、调用存储过程、存储函数的使用、存储过程和函数的查看、修改、删除)

    第15章_存储过程与函数 第15章_存储过程与函数 1. 存储过程概述 1.1 理解 1.2 分类 2. 创建存储过程 2.1 语法分析 2.2 代码举例 3. 调用存储过程 3.1 调用格式 3.2 ...

  5. C#2.0及C#3.0语言规范

    C#2.0语言规范 我的在C:\Program Files\Microsoft Visual Studio 8\VC#\Specifications\2052 C# 3.0 语言规范 附: 从FxCo ...

  6. CSP 1.0 语言规范

    为什么80%的码农都做不了架构师?>>>    点击在线查看wiki版本:CSP1.0语言规范 点击下载PDF版本:CSP1.0语言规范 转载于:https://my.oschina ...

  7. mysql 线程池源码模块_易语言Mysql线程池2.0模块源码

    易语言Mysql线程池2.0模块源码 易语言Mysql线程池2.0模块源码 系统结构:GetThis,初始化,关闭类线程,线程_测试,其他_附加文本,连接池初始化,取mysql句柄,释放mysql句柄 ...

  8. C#3.0语言规范new [Unified C# 3.0 Specification Now Available]

    C#3.0语言规范发布了,感兴趣的同仁的可以抢先下载噢 一个DOC文档,共500多页,不过全都是英文的.呵呵,我英文水平还是可以全篇通读的. 大家看完之后,交流一下,阅读感想吧? Unified C# ...

  9. [翻译] C# 3.0语言规范 收藏

    [翻译] C# 3.0语言规范 收藏 http://lover_p.cstc.net.cn/lover_P/doc/t-csharp3/ 1 具有隐式类型的局部变量 2 扩展方法 2.1 声明扩展方法 ...

  10. Java 版本、语言规范、API、JDK、IDE、Java 源程序编译、执行原理(跨平台性根本原因)、特殊字符用法、8 大数据类型小结

    文章目录 前言 一.三大版本类型 二.Java 语言规范 三.应用程序接口(API) 四.Java 开发工具包(JDK) 五.集成开发环境(IDE) 六.Java 运行环境(JRE) 七.Java 源 ...

最新文章

  1. datatables ajax刷新数据
  2. find 命令_linux中find命令的使用
  3. linux find 忽略大小写查找文件
  4. Python的注释及乱码 || 变量及类型
  5. 时间串变成Date类型的数据
  6. Python程序退出方式小结(亲测)
  7. workflow initialization in webclient ui - Remote call case
  8. Apache Shiro入门
  9. IOS开发基础知识--碎片33
  10. java毕业设计会员刷卡积分管理系统mybatis+源码+调试部署+系统+数据库+lw
  11. 粒子群课设,粒子群算法(基础精讲)ppt课件
  12. Java程序员怎样考察报表工具的开发效率
  13. 公众号吸粉实操之qq群吸粉
  14. Spark 名词解释
  15. 3.NLP中文分词技术
  16. Didn't find class XXX on path: DexPathList [zip file XXX]
  17. seo推广绩效考核指标是什么(新媒体运营的绩效考核指标)
  18. 以Vigoss、Perp、dydx为例,解析衍生品在DeFi2.0进程里的演进
  19. 华为暂缓校招,解散校招群,冲上热搜,引发应届生强烈不满!
  20. OpenBLAS学习一:源码架构解析GEMM分析

热门文章

  1. Debian忘记密码修改
  2. Android_View,ViewGroup,Window之间的关系
  3. window操作大全
  4. 基于简单的路径压缩的并查集算法
  5. 在Mac上使用鼠标键来控制指针的方法
  6. ImageRanger 全萤幕检视操作说明
  7. k8s学习:挂载 pvc
  8. id 和 class 选择器
  9. union介绍,union与struct
  10. nfs文件共享服务器搭建详解