115
http://www.wenyuan.com.cn/webnew/ 10 10 第第 第第第第第 第第 第第第第第 时时时 (: 3 时时6 时时

第 10 章 接口、代理和事件

  • Upload
    levi

  • View
    90

  • Download
    3

Embed Size (px)

DESCRIPTION

第 10 章 接口、代理和事件. (时间: 3 次课, 6 学时). 第 10 章 接口、代理和事件. 前面已经介绍了有关面向对象程序设计的基本实现技能,本章将介绍一些面向对象程序设计的高级技术:接口、代理和事件。接口和代理都属于 C# 语言的引用数据类型,而事件是 C# 语言新增加的一个成员。由于事件和代理有很密切的关系,所以把事件也放在本章介绍。 本章教学目的: 了解接口和类的区别 掌握接口的定义,实现和使用 掌握创建和使用代理的方法 掌握 Delegate 类和 MulticastDelegate 类实现多重代理的方法 掌握创建和使用事件的方法. - PowerPoint PPT Presentation

Citation preview

Page 1: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

第第 1010 章 接口、代理和事件 章 接口、代理和事件

(时间: 3 次课, 6 学时)

Page 2: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

第第 1010 章 接口、代理和事件 章 接口、代理和事件 前面已经介绍了有关面向对象程序设计的基本实现技能,本章将介绍一些面向对象程序设计的高级技术:接口、代理和事件。接口和代理都属于 C# 语言的引用数据类型,而事件是 C# 语言新增加的一个成员。由于事件和代理有很密切的关系,所以把事件也放在本章介绍。 本章教学目的: 了解接口和类的区别 掌握接口的定义,实现和使用 掌握创建和使用代理的方法 掌握 Delegate 类和 MulticastDelegate 类实现多重代理的方法 掌握创建和使用事件的方法

Page 3: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

第第 1010 章 接口、代理和事件章 接口、代理和事件 10.1 接 口 10.2 代 理 10.3 事 件

Page 4: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1 10.1 接 口接 口 10.1.1 接口与类 10.1.2 接口的定义 10.1.3 接口的实现与使用 10.1.4 接口映射 10.1.5 显式接口成员实现 10.1.6 接口实现的继承 10.1.7 接口的重新实现 10.1.8 接口的查询

Page 5: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1 10.1 接 口 接 口 第 9 章中提到, C# 只允许单继承机制。而如果我们希望一个子类继承两个或两个以上更多的父类时, C# 语言是不支持的,即 C# 不允许利用类进行多重继承。但在 C# 语言中,“多重继承”的功能是通过“接口(Interface)” 技术来模拟实现的。

Page 6: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.1 10.1.1 接口与类接口与类 在 C# 中需要实现多重继承时,就要使用接口技术。接口具有类似于抽象类的地位,它只具有“被继承”特性,所以接口也像抽象类一样,是一个最高层次的“基类”。但抽象类只能实现单继承,而接口可实现多继承。因此,在 C# 中,接口和抽象类既有相同的继承特性 ( 最高层“基类” ) ,又有不同的实现机制。 抽象类指的是至少包含一个抽象方法的类,而抽象方法指的是被继承时,必须被重写的方法。而接口是另一种类似于抽象类的引用类型,它主要用来声明要定义的类中将包含哪些功能 ( 方法、属性、索引或事件 ) ,但不包含这些功能的实例化代码 ( 同抽象类 ) ,只在“继承” ( 通常,在接口技术中称为“实现”,以便与类中的“继承”有所区别 ) 时才实例化这些功能的代码 ( 也同抽象类 ) 。换句话说,接口只是定义了类必须做什么,而不是怎样做。 一旦定义了一个接口,许多类都可以实现它。所谓“实现接口”就是意味着某个将要使用这个接口的类,必须为该接口所定义的方法、属性、索引或事件提供实体 ( 实现的代码 ) 。

Page 7: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.1 10.1.1 接口与类接口与类 所以说接口类似于抽象类,但它与类之间有以下区别: 接口只提供类所需实现的方法、属性、索引或事件的格式或约定,不提供任何相应的功能代码。具体的功能代码由继承 ( 使用 ) 该接口的类或结构来实现,这叫作“接口实现”。 接口中只包含方法、属性、索引或事件,而不包含任何数据成员、构造函数、析构函数和静态函数,而且接口中的所有成员都被视为公有,不能有任何访问修饰符。 要实现接口的类必须实现接口中的所有成员,即当一个接口或类从其他接口继承时,它将继承它的基接口中的所有成员。而抽象类则可以根据需要重载部分或全部抽象成员。 接口允许多重继承。一个接口可从多个基接口继承,并包含这些基接口继承树上的所有基接口;一个类可以从多个基接口继承;但一个类最多只能有一个直接父类。

Page 8: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.1 10.1.1 接口与类接口与类 在实际应用中是使用接口还是抽象类为组件提供多态性,一般从以下几点考虑: 如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单易行的方法来控制组件版本。通过更新基类,所有继承类都随更改自动更新。另一方面,接口一旦创建就不能更改。如果需要接口的新版本,必须创建一个全新的接口。 如果创建的功能将在大范围的全异对象间使用,则使用接口。抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能。 如果要设计小而简练的功能块,则使用接口。如果要设计大的功能单元,则使用抽象类。 如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。抽象类允许部分实现类,而接口不包含任何成员的实现。

Page 9: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.2 10.1.2 接口的定义接口的定义 1. 接口定义 接口定义的语句格式为: [代码属性 ] [ 修饰符 ] interface 接口名 [:基接口列表 ] { …… // 接口成员定义体 }

Page 10: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.2 10.1.2 接口的定义接口的定义 2. 接口成员声明 接口成员包括从基接口继承的成员以及接口自身定义的成员。接口成员可以是方法、属性、索引和事件,但不能有常数、运算符、构造函数、析构函数、类型和静态成员。因为接口只具有“被继承”的特性,所以默认时,所有接口成员只具有 public 特性,接口成员的声明中不能含有任何其他修饰符。

Page 11: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.2 10.1.2 接口的定义接口的定义 (1) 接口的方法成员声明格式如下: [代码属性 ] [new] 返回值类型 方法名 ([ 参数 1 ,参数 2 ,… .]); 接口中只能提供方法的格式声明,而不能包含方法的实现,所以接口方法的声明总是以分号结束。 用户可以使用 new修饰符在派生的接口中隐藏基接口的同名方法成员,其作用与类中 new修饰符的作用相同。例如: interface IA { void Math(); } interface IB: IA // 接口 IB继承接口 IA { new void Math(); // 如果不加 new修饰符,将会有警告。加上 new 就可消除 }

Page 12: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.2 10.1.2 接口的定义接口的定义 【例 10.1】定义一个接口 DataSeries ,任何类使用该接口可以产生一系列数字。 public interface DataSeries { int getNext(); //返回数字系列中的下一个数字 void reset(); // 重新开始 void setStart(int x); // 设置初始值 }

Page 13: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.2 10.1.2 接口的定义接口的定义 (2) 接口的属性成员声明格式如下: [代码属性 ] [new] 属性类型 属性名 {get;和 / 或 set;}; 同理,接口中的属性成员不能包含实现,所以只能以分号结束。在接口属性成员中同样也可以使用 new修饰符来隐藏从基接口继承的同名属性成员。接口属性成员的访问方式有只读、只写和可读写 3 种,如表 10.1 所示。

Page 14: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.2 10.1.2 接口的定义接口的定义 访问方式 描 述get; 表示只读属性,即只能读取该属性的值set; 表示只写属性,即只能对属性进行赋值get; set; 表示可读写属性,即可读取属性的值也可以对它进行赋值

表 10.1 接口属性的访问方式

Page 15: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.2 10.1.2 接口的定义接口的定义 【例 10.2】重写接口 DataSeries ,通过类 MyThree 实现接口,同时使用接口属性来获取数列中的下一个元素。 using System; public interface DataSeries // 接口 { int Next // 接口的一个属性 { get; //返回数列的下一个数字 set; // 设置下一个数字 } } class MyThree: DataSeries // 实现接口 DataSeries { int x; public MyThree(){x=0; } // 构造函数 public int Next // 实现接口属性:获取或设置值 { get{ x+=3; return x;} set{ x=value; } } } class App // 应用类 { public static void Main() { MyThree ob=new MyThree (); for (int i=0; i<3; i++) // 通过属性访问接口 Console.WriteLine("Next value is "+ ob.Next); Console.WriteLine("\nStarting at 100"); ob.Next=100; for (int i=0; i<3; i++) // 通过属性访问接口 Console.WriteLine("Next value is "+ ob.Next); } }

Page 16: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.2 10.1.2 接口的定义接口的定义 程序输出结果如下: Next value is 3 Next value is 6 Next value is 9 Starting at 100 Next value is 103 Next value is 106 Next value is 109

Page 17: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.2 10.1.2 接口的定义接口的定义 (3) 接口的索引器成员声明格式如下: [代码属性 ] [new] 数据类型 this [索引器参数类型 参数名 ] {get; 和 / 或 set;} 此格式中的 [代码属性 ]和 new修饰符的用法和作用,与接口的方法成员和属性成员声明的含义完全一样。 声明中的“数据类型”是指索引器引入的元素类型,接口声明中的索引器成员只能用来指定索引器的访问方式,同表 10.1 。同样不允许在索引器参数上使用 out 和 ref 关键字。 例如: interface IA { …… [name("yang yan ") //附加信息 ( 即属性说明 ) int this [int index] { ger; set; } // 索引器定义 }

Page 18: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.2 10.1.2 接口的定义接口的定义 【例 10.3】以下是接口 DataSeries 的另一个版本,其中添加了返回第 i 个元素的只读索引。 using System; public interface DataSeries // 接口 { int Next { get; set; } // 接口的一个属性 int this[int index] // 接口的一个索引,一个只读索引 { get; //返回数列中的指定数字 } } class MyThree: DataSeries // 实现接口 DataSeries { int x; public MyThree(){x=0; } // 构造函数 public int Next // 接口属性的实现。获取或设置值 { get{x+=3; return x;} set{x=value;} } public int this[int index] // 实现索引 { get { x=0; for(int i=0; i<index; i++) x+=3; return x; } }

Page 19: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.2 10.1.2 接口的定义接口的定义 } class App // 应用类 { public static void Main() { MyThree ob=new MyThree (); for (int i=0; i<3; i++) // 通过属性访问接口 Console.WriteLine("Next value is "+ ob.Next); Console.WriteLine("Starting at 100"); ob.Next=100; for (int i=0; i<3; i++) // 通过属性访问接口 Console.WriteLine("Next value is "+ ob.Next); Console.WriteLine("Resetting to 0"); ob.Next=0; for (int i=0; i<3; i++) // 通过索引访问接口 Console.WriteLine("Next value is "+ ob.Next); } }

Page 20: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.2 10.1.2 接口的定义接口的定义 程序输出结果如下: Next value is 3 Next value is 6 Next value is 9 Starting at 100 Next value is 103 Next value is 106 Next value is 109 Resetting to 0 Next value is 3 Next value is 6 Next value is 9

Page 21: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.2 10.1.2 接口的定义接口的定义 (4) 接口的事件成员声明有关“事件”在本章的最后一节介绍,本处只给出接口事件的声明格式: [代码属性 ] [new] event 事件代理名 事件名; 事件声明中的 [代码属性 ]和 new修饰符的用法和作用,与接口的方法成员和属性成员的声明含义完全一样。 例如: Interface IA { …… //其他成员的定义 event Click MyEvent; }

Page 22: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.3 10.1.3 接口的实现与使用接口的实现与使用 以上我们只是定义了一个接口,而在接口中声明的方法、属性、索引或事件的真正实现是由类来完成的。所以说,一旦定义了接口,一个或更多的类就可以以不同的方式来实现该接口中的功能,并且每个类必须实现该接口中所定义的所有方法、属性、索引或事件。即一个接口可以由多个类来实现,而在一个类中也可以实现一个或多个接口。 实现接口的方式与继承相同,即将接口放在类名的后面,中间用冒号隔开。实现接口的语句格式: class 类名:接口名列表 { …… // 类实体 } 其中接口名是指该类所要实现的接口的名称。

Page 23: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.3 10.1.3 接口的实现与使用接口的实现与使用 1. 一个类实现 ( 使用 ) 一个接口。 【例 10.4】创建一个类 MyThree ,实现【例 10.1】中定义的接口 DataSeries ,用于生成一系列公差为 3 的数列。 using System; public interface DataSeries // 接口 DataSeries { int getNext(); //返回数字系列中的下一个数字 void reset(); // 重新开始 void setStart(int x); // 设置初始值 } class MyThree: DataSeries // 实现接口 DataSeries 的类 { int start; int value; public MyThree() // 构造函数 { start=0; value=0; } public int getNext() // 实现接口 DataSeries 中的方法 getNext() { value+=3; return value; }

Page 24: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.3 10.1.3 接口的实现与使用接口的实现与使用 public void reset() // 实现接口 DataSeries 中的方法 reset() { start=0; value=0; } public void setStart(int x) // 实现接口 DataSeries 中的方法 setStart() { start=x; value=x; } } class AppDataSeries // 应用类 { public static void Main() { MyThree ob=new MyThree (); for (int i=0; i<3; i++) //循环输出 3 个数字 Console.WriteLine("Next value is "+ob.getNext()); Console.WriteLine ("Resstting"); // 重新开始 ob.reset(); for (int i=0; i<3; i++) //循环输出 3 个数字 Console.WriteLine("Next value is "+ob.getNext()); Console.WriteLine ("Starting at 100"); //从 100 重新开始 ob.setStart(100); for (int i=0; i<3; i++) //循环输出 3 个数字 Console.WriteLine("Next value is "+ob.getNext()); } }

Page 25: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.3 10.1.3 接口的实现与使用接口的实现与使用 程序的运行结果: Next value is 3 Next value is 6 Next value is 9 Resetting Next value is 3 Next value is 6 Next value is 9 Starting at 100 Next value is 103 Next value is 106 Next value is 109 可见,要实现接口,必须在类名后包括接口,然后提供接口的每一个成员的实现。注意观察上例中接口成员和类中相应的实现的访问类型的写法,在接口成员的声明中不需要任何访问修饰符,而在类中相应接口成员实现定义中则都用 public修饰符。

Page 26: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.3 10.1.3 接口的实现与使用接口的实现与使用 类除了可以用来实现接口外,还可以定义它自己的额外成员,如我们在 MyThree 类中再添加一个方法 getPrevious() ,用于返回前一个数字的值。 class MyThree: DataSeries // 实现接口 DataSeries ,并添加 getPrevious() 方法 { int start; int value; int prev; public MyThree() // 构造函数 { start=0; value=0; prev= -3; } public int getNext() // 实现接口 DataSeries 中的方法 getNext() { prev=value; value+=3;

Page 27: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.3 10.1.3 接口的实现与使用接口的实现与使用 return value; } public void reset() // 实现接口 DataSeries 中的方法 reset() { start=0; value=0; prev=-3; } public void setStart(int x) // 实现接口 DataSeries 中的方法 setStart() { start=x; value=x; prev=x-3; } int getPrevious() //DataSeries 接口没有定义的方法 { return prev; } } 程序中加黑的语句都是由于添加了 getPrevious() 方法,而改变了接口 DataSeries 的方法实现。但是这些方法的接口定义没有任何变化,只是实现接口的类中增加了功能代码,这也是接口的优点之一。

Page 28: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.3 10.1.3 接口的实现与使用接口的实现与使用 2. 多个类实现 ( 使用 ) 一个接口 【例 10.5 】我们再用另一个类 MyFour 来实现接口 DataSeries ,生成一系列公差为 4 的数字序列。 class MyFour: DataSeries // 用另一种方式实现接口 DataSeries { int start; int value; public MyFour() // 构造函数 { start=0; value=0; } public int getNext() // 实现接口 DataSeries 中的方法 getNext() { value+=4;

Page 29: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.3 10.1.3 接口的实现与使用接口的实现与使用 return value; } public void reset() // 实现接口 DataSeries 中的方法 reset() { start=0; value=0; } public void setStart(int x) // 实现接口 DataSeries 中的方法 setStart() { start=x; value=x; } } 可以把此处定义的类加到【例 10.4 】中,则在应用类中的对象可以使用 MyFour 类的方法来生成公差是 4 的数列。具体代码构成,用户可自己组织。由此可以看出 MyThree 和 MyFour 这些类的对象的接口都是 DataSeries 接口,也就是说,任何一个类的对象都可以分别实例化接口中定义的功能信息,即接口实现了 C# 的“一个接口,多种方法”的多态性。

Page 30: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.3 10.1.3 接口的实现与使用接口的实现与使用 3. 一个类实现 ( 使用 ) 多个接口 使用接口而不使用继承的最大的好处就是,可以在同一个类中同时实现多个接口,即实现多重继承。 要实现多个接口,需将这些接口用逗号分开。

Page 31: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.3 10.1.3 接口的实现与使用接口的实现与使用 【例 10.6】在同一个类中实现多个接口。 using System; public interface IShape //" 图形 "接口 IShape { double Area(); // 接口方法,计算图形面积 double GramLength(); // 接口方法,计算图形边长 int Sides {get;} // 接口属性,获取图形的边长 } public interface IShapePlay //输出计算结果的接口 IShapePlay { void Play(); } public class Square: IShape, IshapePlay // 实现两个接口的类 Square ,计算正方形面积 { private int sides; //边数 public int SideLength; //边长 public Square(){ sides=4; } // 构造函数 public int Sides { get { return sides;} } // 实现接口 IShape 中的属性 public double Area() // 实现接口 IShape 中的方法,计算正方形面积 { return ((double) (SideLength* SideLength));

Page 32: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.3 10.1.3 接口的实现与使用接口的实现与使用 } public double GramLength () // 实现接口 IShape 中的方法,计算边长 { return ((double) (Sides* SideLength)); } public void Play() // 实现接口 IShapePlay 中的方法,输出计算结果 { Console.WriteLine("\n 计算正方形面积结果如下: "); Console.WriteLine(" 边长: {0}", this.SideLength); Console.WriteLine(" 边数: {0}", this.Sides); Console.WriteLine("面积: {0}", this.Area()); } } public class MyApp // 应用类 { public static void Main() { Square sq=new Square(); sq.SideLength=8; sq.Play(); } }

Page 33: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.3 10.1.3 接口的实现与使用接口的实现与使用 程序运行结果: 计算正方形面积结果如下: 边长: 8 边数: 4 面积: 64 从Square 类代码可见,由于该类包含了两个接口,所以它必须实现这

两个接口的所有成员。

Page 34: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.4 10.1.4 接口映射接口映射 接口映射是指在实现接口的类或结构中定位接口成员的实现的过程。 在映射过程中,判断类的某个成员 Mc 与接口的相应成员 Mi匹配的原则是: 如果 Mc 和 Mi 是方法,且它们的名称、返回类型和形式参数都一致,则Mc 和 Mi匹配。 如果 Mc 和 Mi 是属性,它们的名称和返回类型都一致,且访问器也相同 ( 如果 Mc 不是显式实现,则允许 Mc 具有额外的访问器 ) ,则Mc 和 Mi匹配。 如果 Mc 和 Mi 是索引器,它们的名称和形式参数都一致,且具有相同的访问器 ( 如果 Mc不是显式实现,则允许 Mc 具有额外的访问器 ) ,则Mc 和 Mi匹配。 现假设在类或结构 C 上执行映射过程,查找接口 I 的成员 M 的实现,查找过程如下: 在类 C 中查找 I.M 的实现。如果 C 中包含一个与 I 和 M 相匹配的显式接口成员实现,则这个成员就被认为是 I.M 的实现。 如果在 C 中没有找到显式接口成员实现,则查找 C 的所有非静态公共成员,如果某个成员与 M 相匹配,则被认为是 I.M 的实现。 如果在 C 中没有找到以上的两种匹配成员,则转到 C 的基类中继续查找,直到找到匹配的实现。如果一个没有找到,将会提示出错。

Page 35: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.4 10.1.4 接口映射接口映射 例如: class MyClass: Interface1 Class1 Interface2 Class2 { void MyMethod1() {……. // 方法体代码 } void MyMethod2() {…… // 方法体代码 } } 以上代码中, MyClass 类使用的 MyMethod1() 方法是在接口 Interface1 中声明的。当执行上述代码时,先在 MyClass 类中查找 Interface1 接口成员的实现,由于在类 MyClass 中找到了 MyMethod1() 方法的实现,所以就终止了查找运算。然而,如果在 MyClass 类没有找到 MyMethod1() 方法的实现,则还将查找基类 Class1 和 Class2 。

Page 36: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.4 10.1.4 接口映射接口映射 如果类或结构实现了多个包含相同成员的接口,则这些成员可能会映射到同一个实现上。在以下代码段中,接口 IA和 IB的 Play 方法成员都被映射到类 C 的 Play() 方法的实现上,由于两个接口的同名方法一定是实现不同的显示目标,则当我们调用 Play() 方法时将会导致歧义性: interface IA // 接口 IA { void Play (); // 方法成员 } interface IB // 接口 IB { viod Play(); // 方法成员 } class C: IA,I B { public void Play() {……} } 所以在应用问题中,应根据需要使用显式接口成员实现,来分别为各个接口的成员提供实现。

Page 37: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.5 10.1.5 显式接口成员实现显式接口成员实现 “ 显式接口成员实现”是指在类中使用接口成员 ( 方法、属性、索引或事件 ) 的完全限定名来定义实现接口成员。接口成员的完全限定名是指依次用点运算符连接命名空间名、最高层基接口名和各层的派生接口名,最后加上成员名构成整个访问名。例如: namesapce MySpace { interface IMyinterface { void MyMethod(); } interface IYouinterface { void YourMethod(); } } 方法 MyMethod() 的完全限定名为 MySpace.IMyinterface.MyMethod() 。

Page 38: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.5 10.1.5 显式接口成员实现显式接口成员实现 创建接口成员的显式实现有两个原因: 一个类有可能要同时实现两个接口,如果每个接口都定义了同名、同类型的成员,则在类中采用完全限定名来显式实现各接口中的相应成员,就可以消除歧义。 使用完全限定名来实现一个接口成员时,在类中该接口成员是私有的。 在上述继承接口 IA和 IB的类 C 中就必须使用显式接口成员实现来消除歧义性: class C: IA,I B { void IA.Play(){……} // 这里不能用 public ,是私有成员 void IB.Play(){……} // 同上

Page 39: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.5 10.1.5 显式接口成员实现显式接口成员实现 【例 10.7】将【例 10.6】计算正方形面积的代码修改如下: using System; public interface IShape { double Area(); // 接口方法,计算图形面积 double GramLength(); // 接口方法,计算图形边长 int Sides {get;} // 接口属性,获取图形的边长 void Play(); // 接口方法 } public interface IShapePlay { void Play(); // 接口方法 } public class Square: IShape, IShapePlay // 实现两个接口的类 Square { private int sides; //边数 public int SideLength; //边长 public Square(){sides=4; }// 构造函数 public int Sides { get { return sides;} }// 实现接口 IShape 中的属性 public double Area() // 实现接口 IShape 中的方法,计算面积 { return ((double) (SideLength* SideLength)); } public double GramLength() // 计算边长 { return ((double) (Sides* SideLength)); }

Page 40: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.5 10.1.5 显式接口成员实现显式接口成员实现 // 显示实现 IShape 接口中的 Play() 方法,输出文本信息 void IShape.Play() //注意:这个显式实现是个私有成员,不能用 public修饰 { Console.WriteLine("\n 计算面积结果如下: "); Console.WriteLine(" 边长: {0}", this.SideLength); Console.WriteLine(" 边数: {0}", this.Sides); Console.WriteLine("面积: {0}", this.Area()); } // 显示实现接口 IShapePlay 中的 Play() 方法,图形显示 void IShapePlay.Play() //注意:这个显式实现是个私有成员,不能用 public 修饰 { Console.WriteLine("显示图形如下: "); } }

Page 41: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.5 10.1.5 显式接口成员实现显式接口成员实现 public class MyApp // 应用类 { public static void Main() { Square sq=new Square(); // 创建一个对象 sq sq.SideLength=8; // 定义一个 IShape 接口变量 sh 。通过强制转换将对象 sq看作一个接口 IShape IShape sh=( IShape)sq; // 定义一个 IShapePlay 接口变量 shp ,原理同上 IShapePlay shp=( IShapePlay)sq; sh.Play(); // 通过接口变量 sh调用显式定义方法 IShape.Play() shp.Play(); // 通过接口变量 shp调用显式定义方法 IShapePlay.Play() } } 程序输出结果: 计算面积结果如下: 边长: 8 边数: 4 面积: 64 图形显示如下:

Page 42: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.5 10.1.5 显式接口成员实现显式接口成员实现 本例使用了方法的完全限定名 ( 接口名 . 方法名 ) ,在同一个类 Square中显式地实现了两个接口 IShape 和 IShapePlay 中的同名方法 Play() ,在应用类中对定义的显式方法进行了调用。对显式实现的方法的调用不能使用“类名 . 方法”的格式,因为用类名调用方法不能明确调用哪个方法,所以,要调用不同接口的某个同名方法时,一般有两个常用的方法: 本例中的方法。将类对象强制转换为相应的接口变量,在本例中就是将类对象 sq强制转换为接口 IShape 或 IshapePlay 的两个相应接口变量sh 和 shp ,可以用这些接口变量来调用相应的接口方法 Play() 。 在实现方法的类中,分别显式实现两个同名方法,由于显示实现的方法是私有的,所以在类中再相应创建两个用于对这两个显式方法进行调用的调用方法,称其为接口引用调用方法。在应用类中则只要像正常情况下用“对象名 . 接口引用调用方法名”调用方法即可。这种方法的代码如下,接口代码同【例 10.8】,以下只给出实现方法的类和应用类代码:

Page 43: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.5 10.1.5 显式接口成员实现显式接口成员实现 public class Square: IShape, IShapePlay // 实现两个接口的类 Square { private int sides; //边数 public int SideLength; //边长 public Square(){sides=4; }// 构造函数 public int Sides { get { return sides;} } // 实现接口 IShape 中的属性 public double Area() // 实现接口 IShape 中的方法,计算面积 { return ((double) (SideLength* SideLength)); } public double GramLength() // 计算边长 { return ((double) (Sides* SideLength)); } // 显示实现 IShape 接口中的 Play() 方法,输出文本信息 void IShape.Play() //注意:这个显式实现是个私有成员,不能用 public修饰 { Console.WriteLine("\n 计算面积结果如下: "); Console.WriteLine(" 边长: {0}", this.SideLength); Console.WriteLine(" 边数: {0}", this.Sides); Console.WriteLine("面积: {0}", this.Area()); }

Page 44: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.5 10.1.5 显式接口成员实现显式接口成员实现 // 显示实现接口 IShapePlay 中的 Play() 方法,图形显示 void IShapePlay.Play() //注意:这个显式实现是个私有成员,不能用 public修饰 { Console.WriteLine("显示图形如下: "); } // 通过接口引用调用方法 Play() public void Play_A() { IShape ob_a=this; //当前对象的引用 ob_a.Play(); //调用 IShape.Play() 方法 } public void Play_B() { IShapePlay ob_b=this; //当前对象的引用 ob_b.Play(); //调用 IShapePlay.Play() 方法 } } public class MyApp // 应用类 { public static void Main() { Square sq=new Square(); // 创建一个对象 sq sq.SideLength=8; sq.Play_A(); //调用 IShape.Play() 方法 sq.Play_B(); //调用 IShapePlay.Play() 方法 } }

Page 45: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.6 10.1.6 接口实现的继承接口实现的继承 1. 接口之间的继承 在 C# 中,一个接口可以继承另一个接口,语法和类的继承一样。当一个类要实现从另一个接口继承来的接口,该类就必须提供接口继承链中定义的所有成员的实现。

Page 46: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.6 10.1.6 接口实现的继承接口实现的继承 【例 10.8】一个接口继承另一个接口。 using System; public interface IA // 接口 IA { void meth1(); void meth2(); } public interface IB:IA // 接口 IB继承接口 IA { void meth3(); } class Myclass: IB // 该类必须实现接口 IA和 IB的所有成员 { public void meth1() { Console.WriteLine("实现 meth1() 方法 "); } public void meth2 () { Console.WriteLine("实现 meth2() 方法 ");

Page 47: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.6 10.1.6 接口实现的继承接口实现的继承 } public void meth3() { Console.WriteLine("实现 meth3() 方法 "); } } class App { public static void Main() { Myclass ob=new Myclass (); ob.meth1(); ob.meth2(); ob.meth3(); } } 程序运行结果: 实现 meth1() 方法 实现 meth2() 方法 实现 meth3() 方法

Page 48: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.6 10.1.6 接口实现的继承接口实现的继承 当一个接口继承另一个接口时,如果派生接口的成员和基接口的成员同名,则基接口的成员就会被隐藏,这与类继承时发生的情况一样。如果派生接口的成员不用 new修饰符,编译时会发出警告信息。加上 new修饰符后即可消除警告。读者可以通过对本例代码的适当修改,来观察隐藏基接口成员的现象。 如果某个接口含有隐藏成员,只有使用 new修饰符的成员可被调用。在以下代码中,接口 ISon 中的方法成员 X()隐藏了基接口 IFather 中的属性成员 X: interface IFather { int X {get;} } interface ISon : IFather { new int X(); // 此处的方法成员 X() 将隐藏基接口中的同名属性成员 X }

Page 49: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.6 10.1.6 接口实现的继承接口实现的继承 对被隐藏的属性成员 X必须提供显式接口成员实现来消除冲突,以下给出了在类中的 3 种不同的实现方法: 两个接口中的成员都使用显式接口成员实现方式来实现 class C : ISon { int IFather.X {get {……}} // 使用显式接口成员方式 int ISon.X() {……} // 使用显式接口成员方式 } 接口 ISon 中的 X成员使用显式接口成员实现方式来实现 class C: ISon { public int X {get {……}} int ISon.X( ) {……} // 使用显式接口成员方式 } 接口 IFather 中的 X成员使用显式接口成员实现方式来实现 class C : ISon { int IFather.X {get {……}} // 使用显式接口成员方式 int X() {……} }

Page 50: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.6 10.1.6 接口实现的继承接口实现的继承 在多重继承的情况下,如果基接口的一个成员被它的一个派生接口中的同名成员隐藏,则该成员也将在这个派生接口的所有派生接口中都处于隐藏状态。例如: interface IFather // 基接口 IFather { void F(int x); } interface IBoy: IFather //派生接口 IBoy { new void F(int x); //隐藏基接口 IFather 中的成员 F() } interface IGirl: IFather //派生接口 IGirl { void G(); } interface IMuilt : IBoy, IGirl { } // 也隐藏 IBoy 的基接口 IFather 中的成员 F() class App { void method(IMuilt y) { y.F(1); //调用 IBoy.F,因为 IFather 成员被派生接口 IBoy 中的 F() 成员所隐藏 ((IFather)y).F(1); //调用 IFather.F ((IBoy)y).F(1); //调用 IBoy.F ((IGirl)y).F(1); //调用 IFather.F, IFather 成员 F()没有被派生接口 IGirl 中的成 员所隐藏 } }

Page 51: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.6 10.1.6 接口实现的继承接口实现的继承 2. 类从基类继承接口映射 类可以从基类继承接口映射,现观察以下代码段: using System; interface IA { void Play(); } class Father: IA // 实现接口 IA { public void Play(){Console.WriteLine(" 调用基类方法 Play()");} } class Son: Father // 继承类 Father { //隐藏基类方法 Play() new public void Play(){Console.WriteLine(" 调用派生类的新方法 Play()");} } class App { public static void Main() { Father f=new Father(); // 创建对象实例 Son s=new Son(); IA fa=f; // 把对象实例赋给接口实例 IA sa=s; f.Play(); //调用 Father.Play() s.Play(); //调用 Son.Play() fa.Play(); //调用 Father.Play() sa.Play(); //调用 Father.Play() } }

Page 52: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.6 10.1.6 接口实现的继承接口实现的继承 程序运行结果: 调用基类方法 Play() 调用派生类的新方法 Play() 调用基类方法 Play() 调用基类方法 Play() 在上述代码中,显然派生类 Son 中的 Play() 方法隐藏了基类 Father 中的 Play() 方法,但这并没有改变 IA.Play() 到 Father.Play() 之间的映射关系。上述代码中 if.Play() 和 is.Play() 语句只调用 Father 类的 Play() 方法,这是因为,派生类 Son 中引入新成员 method() 方法,这并未改变 Father 类和 IA接口之间的映射关系。当然这种情况发生显然不是我们所希望的,我们有时总是希望不同的对象使用不同的实现方法,如何解决这种问题呢?

Page 53: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.6 10.1.6 接口实现的继承接口实现的继承 3. 将接口成员映射到虚方法上 当在接口方法和类的虚方法间创建映射时,其派生类可以重载虚拟方法。所以我们可以将接口方法映射到类中的一个虚方法上,这样,派生类就可通过覆盖基类的虚方法来改变接口的实现,现将上面代码中的类 Father 的 Play() 方法定义为虚方法: using System; interface IA { void Play(); } class Father: IA // 实现接口 IA { // 通过虚方法来实现接口方法 public virtual void Play(){Console.WriteLine(" 调用基类方法 Play()");} } class Son: Father // 继承类 Father { // 在派生类中覆盖基类 Father 的方法 Play() public override void Play(){Console.WriteLine(" 调用派生类的新方法 Play()");} } class App

Page 54: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.6 10.1.6 接口实现的继承接口实现的继承 { public static void Main() { Father f=new Father(); // 创建对象实例 Son s=new Son(); IA fa=f; // 把对象实例赋给接口实例 IA sa=s; f.Play(); //调用 Father.Play() s.Play(); //调用 Son.Play() fa.Play(); //调用 Father.Play() sa.Play(); //调用 Son.Play() ,与上面代码的调用结果不同 } } 程序运行结果: 调用基类方法 Play() 调用派生类的新方法 Play() 调用基类方法 Play() 调用派生类的新方法 Play()

Page 55: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.6 10.1.6 接口实现的继承接口实现的继承 显式接口成员实现时不能被定义为虚成员。但可在显式接口成员实现的过程中调用类的一个虚方法,由于可以在派生类中覆盖这个虚方法,所以同样可以达到调用不同方法的目的。 using System; interface IA { void Play(); } class Father:IA // 实现接口 IA { void IA.Play() // 显示成员实现 { Console.Write(" 调用虚方法 VirtualPlay() : "); VirtualPlay(); } public virtual void VirtualPlay(){Console.WriteLine(" 执行 Father 类中虚方法 VirtualPlay()");} } class Son: Father // 继承类 Father { // 在派生类中覆盖基类 Father 的方法 Play() public override void VirtualPlay(){Console.WriteLine(" 执行派生类的新方法 VirtualPlay()");} }

Page 56: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.6 10.1.6 接口实现的继承接口实现的继承 class App { public static void Main() { Father f=new Father(); // 创建对象实例 Son s=new Son(); IA fa=f; // 把对象实例赋给接口实例 IA sa=s; f.VirtualPlay(); //调用 Father.VirtualPlay() s.VirtualPlay(); //调用 Son.VirtualPlay() fa.Play(); //调用 Father 类中 IA.Play() sa.Play(); //调用 Son.VirtualPlay() } } 程序运行结果: 执行 Father 类中虚方法 VirtualPlay() 执行派生类的新方法 VirtualPlay() 调用虚方法 VirtualPlay() :执行 Father 类中虚方法 VirtualPlay() 调用虚方法 VirtualPlay() :执行派生类的新方法 VirtualPlay()

Page 57: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.6 10.1.6 接口实现的继承接口实现的继承 4. 将接口成员映射到抽象方法上 接口成员不仅可以映射到虚方法上,也可以映射到抽象方法上 ( 也叫纯虚方法 ) ,所以抽象类也可以实现接口。 interface IA { void F(); void G(); } abstract class AbsClass: IA // 使用抽象类实现接口 { public abstract void F(); public abstract void G(); }

Page 58: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.7 10.1.7 接口的重新实现接口的重新实现 接口的重新实现是指某个类可以再次实现已被它的基类实现的接口,即继承借口实现的类可以重新实现该接口。其方法是把接口的名称置于基类列表中,当重新实现接口后,派生类从基类继承的接口映射不会影响为接口的重实现建立的接口映射。

Page 59: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.7 10.1.7 接口的重新实现接口的重新实现 【例 10.9】在以下代码中,类 CFather 实现接口 IFather ,类 CSon从类 CFather派生,并重新实现了 IFather 接口的部分成员 (Play() 方法和 Clear() 方法 ) 。 using System; namespace Iface { public interface IFather // 接口 IFather { int x { get; set; } // 属性 int y { get; set; } void Play(); // 方法 void Clear(); } class CFather : IFather // 类 CFather { public int x // 实现接口 IFather 中的属性 x { get { Console.WriteLine("在 CFather 类中实现属性 x"); return 0; } set { Console.WriteLine("在 CFather 类中实现属性 x"); } } public int y // 实现接口 IFather 中的属性 y { get { Console.WriteLine("在 CFather 类中实现属性 y"); return 0; } set

Page 60: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.7 10.1.7 接口的重新实现接口的重新实现 { Console.WriteLine("在 CFather 类中实现属性 y"); } } public void Play() { Console.WriteLine("在 CFather 类中实现 Play() 方法 "); } public void Clear() { Console.WriteLine("在 CFather 类中实现 Clear() 方法 "); } } class CSon: CFather , IFather { private int mx; private int my; private string mtext; new public void Play() // 重新实现接口 IFather 中定义的 Play() 方法 { Console.WriteLine("在 CSon 类中实现 Play() 方法 "); } public string Text { get{return mtext;} set{mtext=value;} } new public void Clear() // 重新实现接口 IFather 中定义的 Clear() 方法 { this.Text=""; Console.WriteLine("在 CSon 类中实现 Clear() 方法 "); } }

Page 61: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.7 10.1.7 接口的重新实现接口的重新实现 class App { public static void Main(string [ ] args) { CSon ctext=new CSon(); Console.WriteLine("通过类 CSon 的实例调用接口成员 "); ctext.x=0; // 由于类 CSon 中没有实现属性 x,所以调用其基类 CFather 中的 x属性实现 ctext.y=0; //原因同上。 ctext.Play(); // 类 CSon 中的 Play 方法覆盖其基类 CFather 中的 Play 方法 ctext.Clear(); //原因同上。 Console.WriteLine(); Console.WriteLine("通过接口 IFather 的实例调用接口成员 "); IFather itext=ctext; itext.x=1; // 接口 IFather 的属性 x被映射到类 CFather 中实现 itext.y=1; // 原因同上 itext.Play(); // 接口 IFather 中的 Play 方法被映射到派生类 CSon 中 Play 方 法的实现上 itext.Clear(); //原因同上 Console.WriteLine(); Console.WriteLine("通过类 CFather 的实例调用接口成员 "); CFather obtext=ctext; // 通过类 CFather 的对象调用的接口成员全部是类 CFather 提供的实现, // 说明 CFather 类的接口映射没有被 CSon 类的重新实现所改变 obtext.x=1; obtext.y=1; obtext.Play(); obtext.Clear(); } } }

Page 62: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.7 10.1.7 接口的重新实现接口的重新实现 程序运行结果: 通过类 CSon 的实例调用接口成员 在 CFather 类中实现属性 x 在 CFather 类中实现属性 y 在 CSon 类中实现 Play() 方法 在 CSon 类中实现 Clear() 方法 通过接口 IFather 的实例调用接口成员 在 CFather 类中实现属性 x 在 CFather 类中实现属性 y 在 CSon 类中实现 Play() 方法 在 CSon 类中实现 Clear() 方法 通过类 CFather 的实例调用接口成员 在 CFather 类中实现属性 x 在 CFather 类中实现属性 y 在 CFather 类中实现 Play() 方法 在 CFather 类中实现 Clear() 方法 我们通过本例可以看到,在重新实现的接口映射中,将会查找继承的公有成员和显式接口成员实现,所以映射过程将会首先在派生类中查找匹配成员。如果派生类没有为重新实现接口的所有成员提供所有的实现,则映射过程会接着在基类中查找相应的匹配成员,所以在本例中 CSon 类中的 x和 y 属性会使用其基类 CFather 提供的实现。

Page 63: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.1.8 10.1.8 接口的查询接口的查询 class App { public static void Main() { MyBook mb=new MyBook (); if( mb is Billing) // 将检查类 MyBook是否实现了接口 Billing 的成员 { Billing bill=(Billing)mb; Bool status=mb.Calculate_Discount(); } else { Console.WriteLine("不支持接口 "); } } } 程序运行结果为:计算折扣值。

Page 64: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2 10.2 代 理 代 理 10.2.1 代理的概念 10.2.2 代理的定义 10.2.3 代理的使用 10.2.4 Delegate类和MulticastDelegate

类 10.2.5 多重代理的实现

Page 65: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2 10.2 代 理 代 理

Page 66: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.1 10.2.1 代理的概念代理的概念 在 C/C++中,调用函数有两种方式,一种是通过函数名调用;另一种是利用函数指针 ( 函数的入口地址 ) 来调用函数。采用第二种函数调用方式的目的,是通过一个指向函数的指针变量 (存放函数指针 ) 来灵活调用多个不同功能的方法。例如我们要求多个不同被积函数的定积分,积分的方法是一样,只是被积函数不一样,具体做法是:除了定义一系列被积函数外,再定义一个求积分的公用函数,在这个函数中定义一个指向函数的指针变量,用来存放函数的入口地址 ( 函数指针 ) ,并在此函数中通过该指针变量指向不同的被积函数入口从而求出不同的定积分。另外,在 C/C++中函数指针仅仅是一个内存地址,这个地址不包含任何有关函数的参数、返回值以及调用约定等信息,因此它不是类型安全的。 在 C# 中,代理 (delegate) 的作用类似于 C/C++中的函数指针,并且它是面向对象的和类型安全的。在 C# 中的方法类似于 C/C++中函数,所以方法在内存中也有一个入口物理地址,它就是方法被调用的地址。方法的入口地址可以赋给“代理”,通过“代理”调用该方法,而且同一个代理可以调用多个不同的方法。所以使用代理可以在程序运行期调用所需的某个方法,而不用在编译时固定指出调用什么方法。这个特性在创建一个可动态插入组件的框架时十分有用,例如在一个图画程序 ( 如Windows附件中的图画 ) 中,使用代理可以让用户插入一个特殊的过滤器和图象分析器,而且,用户可以创建一系列这样的过滤器和分析器。另外,代理也是实现 .NET框架中事件处理机制的基础。

Page 67: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.2 10.2.2 代理的定义代理的定义 在 C# 中,代理同类和接口一样,也是一种引用类型。用关键字 delegate 定义一个代理,格式如下: [代码属性 ] [ 修饰符 ] delegate 返回类型 代理名 ([ 形式参数列表 ]); 其中: “[代码属性 ]” 用于指出有关被定义的代理的附加说明性信息。 “[ 修饰符 ]” 可以是 new 和 4 个访问修饰符 (public 、 protected、 internal 和 private) : 使用 new修饰符定义代理,则表明当前定义的代理隐藏继承来的同名代理。 public :所有对象和类型都可以无条件地访问所定义的代理。 protected:只允许定义代理的类及其派生类能访问该代理。 internal :只有代理所属的工程项目的成员能访问该代理。 private :只有定义代理的类能访问该代理。 代理名可以是 C# 的任意合法的标识符。 返回类型是指代理所指向方法的返回值的类型。只有代理的标识和返回类型与代理所指向的方法的标识和返回值类型一致时,才能成功使用该代理,所以代理的返回类型必须与它所指向的方法的返回类型一致。 形式参数列表用于指出代理所指向方法的参数列表,这个列表必须与代理所指向方法的参数列表中的参数个数及其参数类型一致,包括形参的顺序、个数和类型。如果代理所指向的方法本身没有参数,则此处代理定义中也不要任何参数。

Page 68: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.2 10.2.2 代理的定义代理的定义 我们可以在任何全局范围内 ( 某个命名空间 ) 、一个类或其他类型的内部定义代理。但由于代理是隐式封闭的,所以不可以派生新的代理。 例如,在一个类中定义一个代理 MyDelegate : class MyClass { …… // 类的其他成员定义 public delegate void MyDelegate(string s); } 此代理定义中 void表示代理指向的方法不返回任何值,参数 string s表示代理指向的方法将接受一个字符串。 从代理的定义格式可以看出,它和定义一个抽象类的抽象方法的格式相像,只是定义的关键字不同而已,把 abstract 现换成了 delegate 。 在 C# 中,当定义了一个代理,如 MyDelegate ,则 C#编译器就会根据代理的定义语句自动生成一个从 System. MulticastDelegate 类 ( 将在后面介绍 )派生的类 MyDelegate 。MyDelegate 类有一个构造函数 ( 在本处为 public MyDelegate(object target, Int32 methodPtr);) , C#自动生成的代码示意如下:

Page 69: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.2 10.2.2 代理的定义代理的定义 public class MyDelegate : System.MulticastDelegate { public MyDelegate(object target, Int32 methodPtr); // 构造函数 // 以下方法的原型与代理定义一致 // 此方法用来调用代理实例所封装的方法 public void virtual Invoke(Object value, Int32 item, Int32 numItems) {……} // 以下两方法用于执行异步调用 public virtual IAsyncResult BeginInvoke( Object value, Int32 item, Int32 numItems , AsyncCallback callback , Object object) ; Public virtual void EndInvoke ( IasyncResult result ) {……} ……. } 在上述构造函数中有两个参数:前者 target变量是调用方法的对象,如果代理对象要调用的是实例方法,那么 target 就是定义实例方法的类的实例。而如果代理要调用的方法是静态方法,则 target 就为 null;后者methodPtr变量是代理要调用的方法,这个变量的值不能为 null值。由此我们可以知道代理是如何实现其代理服务的。

Page 70: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.3 10.2.3 代理的使用代理的使用 我们在本节的开头已经说明在 C/C++中,同代理最接近的概念是函数指针,不过代理的使用范围比函数指针的使用范围更广。函数指针只能引用静态方法,而代理不仅可以引用静态方法 (静态方法是与类相关的方法 ) ,而且还可以引用对象的实例方法 ( 实例化方法是与类的对象实例相关的方法 ) 。而且代理是面向对象,且类型安全的。 由此可知,代理的最大作用就是它可以在运行期决定调用哪个方法,并且不但可以调用对象的实例化方法,还可以调用类的静态方法。但使用代理的最重要的特征是,代理只是检查要调用的方法是否与代理的标识相匹配,因为代理只能调用和其定义特征相符的方法,所以代理可以执行匿名方法调用。 代理虽然规定了标识和返回类型,但这些都只能由方法来实现。从这一点看,代理类似于接口。代理和接口都允许方法的声明和方法的实现相分离,也就是说,都是先声明方法,然后由其他对象来实现。但是代理与接口在使用上有所不同:当调用单一方法或要为指定的标识和返回类型实现一些方法时,通常使用代理;而接口用于声明多个带有不同的标识和返回类型的相关方法。

Page 71: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.3 10.2.3 代理的使用代理的使用 1. 使用代理的程序结构 (1) 定义一个代理。 (2) 使用 new运算符创建一个“代理对象”,同时为该代理对象指出调用的方法名。这个过程又称为代理的实例化。 (3) 应用程序中像使用方法名一样使用“代理对象”来调用方法。

Page 72: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.3 10.2.3 代理的使用代理的使用 2. 代理的实例化 在 C# 中代理虽然可以在类中定义,看作是类的一个成员,但它本身是一个类,所以使用代理前也必须实例化,即创建一个代理对象,同创建类对象一样用 new运算符创建相应的代理对象。按理说我们在创建代理实例时,必须提供上述创建代理对象构造函数的两个参数 target 和 methodPtr ,但 C# 为创建代理实例提供了简化方法,只需提供所调用方法的完全限定名,由 C#编译器再从中分解出调用方法的对象和调用的方法。有以下两种情况: 当代理实例调用的是实例方法时,方法的完全限定名由对象名加上点运算符再加上方法名: 对象名 . 实例方法名 当代理实例引用的是静态方法时,方法的完全限定名由类名加上点运算符再加上方法名: 类名 . 静态方法名

Page 73: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.3 10.2.3 代理的使用代理的使用 【例 10.11】代理的使用。 using System; delegate int MyDelegate(int x); // 定义一个代理 public class MyClass // 定义一个类 { public static int StaticMethod(int x) //静态方法 { Console.WriteLine("类的静态方法: {0}", x); return 0; } public int InstanceMethod(int x) // 实例方法 { Console.WriteLine("类的实例方法: {0}", x); return 0; } } public class App { public static void Main() { MyClass c=new MyClass (); // 创建一个类 MyClass 的对象 MyDelegate d1=new MyDelegate (c. InstanceMethod); // 创建代理对象 d1, 调用实例方法 MyDelegate d2=new MyDelegate (MyClass. StaticMethod); // 创建代理对象 d2, 调用静态方法

Page 74: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.3 10.2.3 代理的使用代理的使用 d1(6666); d2(8888); } } 运行结果: 类的实例方法: 6666 类的静态方法: 8888 在本例的开始定义了一个代理,注意这个代理不属于任何一个类,而且也没有定义在任何的命名空间内,所以这样定义的代理属于 C#默认的命名空间——全局命名空间 Global Namespace 。 使用代理调用实例方法要用 3条语句:首先创建一个对象实例,再由对象名和实例方法名组成参数创建一个代理,最后由此代理调用这个方法。上例中的这 3条语句是: MyClass c=new MyClass (); // 创建一个类 MyClass 的对象 MyDelegate d1=new MyDelegate (c. InstanceMethod); // 创建代理对象 d1, 调用实例方法 d1(6666); 使用代理调用静态方法只要用两条语句,创建一个调用静态方法的代理,参数由类名和方法名组成,然后由此代理调用这个静态方法,上例中的这 2条语句是: MyDelegate d2=new MyDelegate (MyClass. StaticMethod); // 创建代理对象 d2,引用静态方法 d2(8888);

Page 75: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.3 10.2.3 代理的使用代理的使用 在上例中,如果把代理的定义语句放在类 MyClass 中,相应代码修改如下: using System; public class MyClass // 定义一个类 { delegate int MyDelegate(int x); // 定义一个代理 …… //其他代码同上 } 这种情况下,代理是类的一个成员,需要使用类名来创建代理和使用代理,所以在 Main()方法中使用代理调用方法的代码也要进行相应的修改: // 使用代理调用实例方法 MyClass.MyDelegate d1=new MyClass.MyDelegate (c. InstanceMethod); // 使用代理,引用静态方法 MyClass.MyDelegate d2=new MyClass .MyDelegate (MyClass.Static Method);

Page 76: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.3 10.2.3 代理的使用代理的使用 如果将 MyClass 作为 App 类的父类,则可以直接在子类中使用父类定义的代理,而不需要每次都使用“类名 . 代理名”来使用代理,代码如下: using System; public class MyClass // 定义一个类 { delegate int MyDelegate(int x); // 定义一个代理 public int InstanceMethod(int x) { Console.WriteLine("类的实例方法: {0}", x); return 0; } } public class App: MyClass { public static void Main() { MyClass c=new MyClass (); // 创建一个类 MyClass 的对象 MyDelegate d1=new MyDelegate (c. InstanceMethod); // 创建代理 d1 ,不需用类名 // 使用代理,代理所要调用的方法名属性 ( 代理要调用的方法名 ) Console.WriteLine("Method:{0}", d1.Method); // 使用代理,代理所要调用的对象名称属性 (请求代理的对象所属的类或代理调用方法所属的类 ) Console.WriteLine("Target:{0}", d1.Target); } }

Page 77: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.3 10.2.3 代理的使用代理的使用 程序运行结果为: Method: Int32 InstanceMethod(Int32) Target: MyClass 在本例的最后两个语句行,使用了 System命名空间的 Delegate 类 ( 该类是所有构成代理的类的基类 ) 的两个属性 Method和 Target 。从上述运行结果,属性 Method将给出方法的返回类型、方法名称和方法的参数列表;属性 Target给出对象所属的类。 在创建代理实例时,我们也可以把其他的代理实例作为参数。使用这种方式创建的代理实例与作为参数的代理实例将引用相同的方法,所以这两种方式创建的代理类型所定义的方法原型必须一致。这种方式创建代理实例的代码是: MyDelegate d2=new MyDelegate(d1); 代理只能用于调用方法,不能调用属性、索引器、自定义运算符、构造函数和析构函数。

Page 78: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.4 Delegate10.2.4 Delegate 类和类和 MulticastDelegateMulticastDelegate 类类 由前述已知,所有的代理类都是从MulticastDelegate 类派生的,而 MulticastDelegate 类又是从 Delegate 类派生的,它们都位于 System命名空间下。 在 C# 中一般的代理实例 ( 指一个代理仅可以调用一个方法 ) 被默认为 Delegate 类的对象,所以我们通常使用 delegate 关键字来定义代理,利用 new运算符来创建代理实例,然后使用 Delegate 类的方法和属性管理代理实例,它的定义原型为: public abstract class Delegate : ICloneable, ISerializable 而 MulticastDelegate 类是用来支持多重代理的,其调用列表中可以拥有多个方法的代理。它的定义原型为: public abstract class MulticastDelegate : Delegate 多重代理是指将一组代理组成一个集合,由 MulticastDelegate 类的一个对象来管理这个代理集合,利用这个代理集合执行多个方法,这个功能叫多播,我们将在下一节介绍如何使用多播。在本节我们只介绍 C# 中是如何由 MulticastDelegate 类来实现多重代理 ( 多 播 ) 的。

Page 79: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.4 Delegate10.2.4 Delegate 类和类和 MulticastDelegateMulticastDelegate 类类 1. Delegate 类 Delegate 类中有两个公共只读属性: Method属性:该属性用于获得代理实例要调用的静态方法名。可用以下代码获得代理对象所调用的方法名: string MethodName=delegate.Method.Name ; Target 属性:该属性获得类实例,当前代理将对其调用实例方法。如果代理调用的是静态方法,则该属性为 null 。以下代码可用于返回代理实例所调用的方法的类型: string objType=delegate.Target.GetType();

Page 80: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.4 Delegate10.2.4 Delegate 类和类和 MulticastDelegateMulticastDelegate 类类 2. MulticastDelegate 类 MulticastDelegate 类继承 Delegate 类中 Method和 Target 两个属性,另外还有以下成员,用于组织代理集合,实现多重代理: 两个重载运算符 ==和! =:它们都是静态的,用于比较两个代理实例是否相等。代码为: if (delegate1==delegate2) {……} if (delegate1!=delegate2) {……} Equals 方法:用于判断两个代理实例是否相等。代码为: if (delegate1.Equals(delegate2)) {……} CombineImpl 方法:受保护的方法。用于把当前代理与指定的代理实例的调用链表合并起来,返回一个新的代理实例,该代理实例将包含一个改变后的新调用链表。代码为: // 把两个代理实例的调用链表合并成为一个新的调用链表 MyDelegate newDelegate= MyDelegate.CombineImpl(delegate1,delegate2); RemoveImpl 方法:受保护的方法。用于从多重代理的调用链表中删除一个指定的代理对象,返回一个新的代理实例,该代理实例将包含一个改变后的新调用链表。代码为: //从delegate1 的调用链表中删除代理实例 delegate2 而成为一个新的调用链表 MyDelegate newDelegate= MyDelegate.RemoveImpl(delegate1,delegate2); 用户定义的代理都是从MulticastDelegate 类派生的,所以用户可以直接在代理实例中使用以上所述MulticastDelegate 类中的属性和方法成员。在实际编程时,我们通常不显式调用 CombineImpl 和 RemoveImpl 方法,而是使用 C#的重载运算符 +=和 -=来分别调用 CombineImpl 和 RemoveImpl 方法 ( 实际上,“ +” 和“ -”运算符也是通过调用 CombineImpl 和 RemoveImpl 方法来实现的 ) ,分别对应于以下 代码: delegate1 +=delegate2; //合并两个代理实例 delegate1 -=delegate2; //从一个多重代理中删除一个代理实例

Page 81: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.4 Delegate10.2.4 Delegate 类和类和 MulticastDelegateMulticastDelegate 类类 MulticastDelegate 类实现多重代理 ( 多播 ) 的机制是: (1) 从MulticastDelegate 类派生的用户代理实例中含有一个调用链表,这个链表将由多个代理实例组成,其中每个代理实例都封装了一个相应的方法,这意味着一个代理实例可以同时调用多个方法。那么代理实例是如何实现把多个代理实例连接起来构成调用链表的呢?代理实例是通过私有指针变量 _prev来连接多个代理构成链表的,另外代理实例还含有 _target 和 _methodPtr 两个私有指针变量,它们分别表示代理实例调用的实例和方法指针,用于比较调用链表中相同位置上的两个代理实例是否相同。 (2) 创建一个新的代理实例时,变量_prev被设置为 null ,表示链表中没有其他的代理实例。而当用户使用 Combine 方法把另一个代理实例合并到该调用链表中时,将先建立一个含有 _target 和 _methodPtr值的新实例,然后把该实例的 _prev 变量设为调用链表的头实例,即从调用链表的头插入新实例。而调用链表的尾是最早加入链表的代理实例,最后返回新创建的代理实例,如图 10.1 所示。逆向构成调用链表,所以链表中的最后一个实例的 _prev的值为 null 。 使用 Remove 方法从调用链表中删除一个代理实例的机制与合并链表类似,也是通过重新创建新的代理实例来实现所要求的功能。 (3) 比较两个代理实例的调用链表是否相同的方法是:首先调用链表的顺序要相同;然后是对应元素表示的目标对象和方法要相同 ( 即 _target 和 _methodPtr 两个变量的 值 ) :如果方法是静态方法,则它们必须是同一个类中的同一个方法;如果方法是实例方法,则它们必须是同一个实例的同一个方法。

Page 82: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.4 Delegate10.2.4 Delegate 类和类和 MulticastDelegateMulticastDelegate 类类

图 10.1 代理实例中的调用链表

Page 83: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.4 Delegate10.2.4 Delegate 类和类和 MulticastDelegateMulticastDelegate 类类 3. 代理的调用实现机制 如果假设我们定义的代理是 MyDelegate ,则当我们通过调用这个代理去调用方法时, C#编译器将会根据我们的代理定义语句,自动生成一个从System.MulticastDelegate 类派生的类 MyDelegate( 上节中提到 ) , C#自动生成的部分代码如下: public class MyDelegate : System.MulticastDelegate { public MyDelegate(Object target, Int32 methodPtr); // 构造函数 // 以下方法的原型与代理定义一致 // 此方法用来调用代理实例所封装的方法 public void virtual Invoke(Object value, Int32 item, Int32 numItems) { // 如果链表中存在其他的代理实例,则调用当前实例之前的代理实例 if(_prev!=null) _prev.Invoke(value, item, numItems); //然后再调用当前代理实例要调用的方法 return _target.methodPtr(value, item, numItems); } } 从上述代码中可看出,当调用一个代理时,编译器将生成一个 Invoke 方法,该方法就用于完成用代理实例来调用方法的功能。当调用一个代理时,将首先调用位于它之前的调用链表中的代理实例;而返回的是最后一个加入的 ( 位于链表头部的 ) 代理实例的返回值。 在上述调用机制中,当链表中某个实例的调用出现异常时,整个链表的调用将被终止,所以这种调用机制虽很简单,但却不很健壮。所以在 C# 中,类 MulticastDelegate还提供了一个 GetInvocationList 方法,该方法通过一个数组来返回调用链表中的所有代理实例。这样,我们就可以获取每个代理实例的返回值,并针对每个代理实例进行异常处理。 GetInvocationList 方法的使用见【例 10.3】。

Page 84: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.4 Delegate10.2.4 Delegate 类和类和 MulticastDelegateMulticastDelegate 类类 【例 10.12】GetInvocationList 方法的使用。 using System; namespace AppDelegate { delegate void MyDelegate(); // 定义代理 class MyClass { public static void StMethod1() { Console.WriteLine(" 调用第一个静态方法 "); } public static void StMethod2() { Console.WriteLine(" 调用第二个静态方法 "); } public void InsMethod1() { Console.WriteLine(" 调用第一个实例方法 "); } public void InsMethod2() { Console.WriteLine(" 调用第二个实例方法 "); } } class App { // 显示指定代理实例的调用链表和该代理实例对应的目标和调用的方法 static void PlayDelegate(MyDelegate d) {

Page 85: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.4 Delegate10.2.4 Delegate 类和类和 MulticastDelegateMulticastDelegate 类类 Console.WriteLine("代理 {0}的调用链表为: ", d); Delegate[ ] dp=d.GetInvocationList(); // 建立代理 d的调用链信息数组dp for( int i=0; i<dp.Length; i++) { Console.WriteLine("第 {0}个元素的目标为: {1}", i , dp[i].Target==null ? "null" : dp[i].Target); Console.WriteLine(" 调用方法为: {0}", dp[i].Method); } Console.WriteLine(" 当前的目标为 {0}, 调用方法为 {1}", d.Target==null ? "null" : d.Target ,d.Method); } static void Main(string[ ] args) { MyClass myobj=new MyClass ();// 创建包含调用方法的实例 // 创建调用方法的代理实例 MyDelegate myd=new MyDelegate (MyClass.StMethod1); // 向代理实例的调用链表中添加代理要调用的其他方法 myd +=new MyDelegate(MyClass.StMethod2); myd +=new MyDelegate(myobj.InsMethod1); myd +=new MyDelegate(myobj.InsMethod2); PlayDelegate(myd); // 显示代理实例 myd的调用链表 // 通过代理实例调用的方法 Console.WriteLine(); Console.WriteLine("代理实例调用的方法有: "); myd(); //调用多播 } } }

Page 86: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.4 Delegate10.2.4 Delegate 类和类和 MulticastDelegateMulticastDelegate 类类 运行结果为: 代理 AppDelegate. MyDelegate 的调用链表为: 第 0 个元素的目标为: null 调用方法为: void StMethod1() 第 1 个元素的目标为: null 调用方法为: void StMethod2() 第 2 个元素的目标为: AppDelegate.MyClass 调用方法为: void InsMethod1() 第 3 个元素的目标为: AppDelegate.MyClass 调用方法为: void InsMethod2() 当前的目标为 AppDelegate.MyClass 调用的方法为 void InsMethod2() 代理实例调用的方法有: 第一个静态方法 第二个静态方法 第一个实例方法 第二个实例方法 在本例中,先创建了一个包含调用方法的对象实例;然后创建了一个将要调用这个对象中方法的代理实例,并向该代理实例的调用链表中添加其他代理实例;最后显示代理实例的调用链表,并通过代理实例来调用方法。从上述结果可见,被调用的方法顺序与代理实例的调用链表的顺序是一致的。

Page 87: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.5 10.2.5 多重代理的实现多重代理的实现 由上所述,在 C# 语言中一个代理实例 ( 对象 ) 含有一个调用链表,该链表可以包含多个该代理要调用的方法,这种机制用于实现一个代理实例可同时调用多个方法的功能,这就是在 C# 中代理的一个最吸引人的特性——多重代理 ( 又叫多播 ) 。多播具有创建方法链表的能力,当调用代理时,所有被链接的方法都会被自动调用,即使用多播可以在一次代理调用中调用方法链上的所有方法。创建这种方法链表的方法是:先实例化一个代理,然后使用 += 运算符把方法添加到链表中;要从链表中删除一个方法,就使用 -= 运算符。使用多播惟一的限制是:方法链表中的方法必须具有相同的参数,而且这些方法的返回类型必须定义为 void。

Page 88: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.5 10.2.5 多重代理的实现多重代理的实现 【例 10.13】使用多重代理 ( 多播 ) 。 using System; delegate void StringDelegate(ref string str); // 定义一个代理 class StringOps // 定义一个类,进行字符串操作 { static void ReplaceSpaces(ref string s) //静态方法,用连字符替换空格 { Console.WriteLine("用连字符替换空格 "); s=s.Replace(' ' , '- '); // Replace() 方法专门用于用连字符替换空格 } static void RemoveSpaces(ref string s) //静态方法,删除空格 { string temp="" ; int i; Console.WriteLine("删除空格 "); for (i=0; i<s.Length; i++) if(s[i]!=' ') temp +=s[i]; s=temp; } static void Reverse(ref string s) //静态方法,字符串反序 { string temp="" ; int i, j; Console.WriteLine(" 字符串反序 "); for (j=0, i=s.Length-1 ; i>=0; i--, j++) temp +=s[i]; s=temp; } public static void Main() {

Page 89: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.5 10.2.5 多重代理的实现多重代理的实现 // 创建代理 StringDelegate strdeligate; // 创建一个空值代理 // 创建引用 ReplaceSpaces 方法的代理 StringDelegate replacesp=new StringDelegate (ReplaceSpaces); StringDelegate removesp=new StringDelegate (RemoveSpaces); StringDelegate reversestr=new StringDelegate (Reverse); string str="I am a teacher."; // 创建多播,用于调用 ReplaceSpaces() 和 Reverse() 方法 strdeligate = replacesp; strdeligate += reversestr; // 用相应的代理建立方法链表 ( 因代理代表着一个方法 ) //调用多播 strdeligate (ref str); //调用多播:先调用 ReplaceSpaces() 方法,再调用 Reverse() 方法 Console.WriteLine("操作字符串结果: "+str); // 显示结果 Console.WriteLine(); //删除ReplaceSpaces 方法,加入RemoveSpaces 方法 strdeligate -= replacesp; //删除ReplaceSpaces 方法 strdeligate += removesp; // 建立另一个多播,加入RemoveSpaces 方法 str="I am a teacher."; // 重新设定字符串 //调用多播 strdeligate (ref str); Console.WriteLine("操作字符串结果: "+str); // 显示结果 Console.WriteLine(); } }

Page 90: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.2.5 10.2.5 多重代理的实现多重代理的实现 运行结果如下: 用连字符替换空格 字符串反序 操作字符串结果: rehcaet-a-ma-I 字符串反序 删除空格 操作字符串结果: rehcaetamaI 创建和使用多播的过程是:先把 strdeligate 的值置为 replacesp 的引用,然后使用 +=来添加 reversestr 引用,则当调用 strdeligate 时,这两个方法都得到了调用,先把空格替换为连字符,然后再把字符串变为逆序输出。接着又把 replacesp从方法链表中删除,并把 removesp 加入链表。这样当再次调用 strdeligate 时,执行的是先删除空格,然后再把字符串变为逆序输出。在本例中使用 ref参数将会给调用者返回改变后的字符串。 从上述程序代码中可见,只用一条语句“ strdeligate (ref str);” 就执行了多个方法,即多重代理可以通过一条语句执行多个方法。

Page 91: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3 10.3 事 件 事 件 10.3.1 事件的概念 10.3.2 创建事件和使用事件 10.3.3 多播事件

Page 92: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3 10.3 事 件 事 件

Page 93: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.1 10.3.1 事件的概念事件的概念 有关“事件 (Even)” 的概念,对于熟悉Windows 用户界面编程的程序员来说并不陌生。例如,在实际应用中,我们希望在“单击某个按钮”后,系统能对此“单击”事件作出某种反应,如显示一张图表或播放一段音乐等,这就是一个典型的事件发生和事件处理过程。我们把“单击”按钮的动作叫触发事件,显示图表或播放音乐叫处理“单击”事件。 在 C# 语言中,事件的发生不仅仅由用户的操作驱动,如单击按钮或单击鼠标或选择菜单等;事件还可以由程序逻辑触发,如类的某个对象的状态发生的变化,导致程序对这种变化做出某种处理等。触发事件的对象称为事件发送者 (触发事件的对象 );接收事件的对象称为事件接收者 (处理事件的方法 ) 。

Page 94: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.1 10.3.1 事件的概念事件的概念 在 C# 语言中,事件机制有以下特征: 事件和变量、方法、属性一样,也是类的一种成员。使用事件机制可以实现:当类对象的某个状态发生变化 ( 事件被触发 )时,系统将会通过某种“途径”调用类中的有关处理这个事件的方法。 在 .NET框架中,事件是将事件发送者 (触发事件的对象 ) 与事件接收者 (处理事件的方法 ) 相关联的一种代理类,即事件机制是通过代理来实现的。当一个事件被触发时,由该事件的代理来通知 (调用 )处理该事件的相应方法。 事件也支持多播 ( 多重代理 ) 。一个事件发送者 (触发事件的对象 ) 可以同时触发多个处理事件的方法。

Page 95: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.2 10.3.2 创建事件和使用事件创建事件和使用事件 我们知道在 C# 中把事件作为一个代理类,并让它能根据系统的各种状态变化自动实现跟踪和处理,即事件相当于一个或多个方法的代理。当对象的某个状态发生了变化,代理就会被自动调用,并执行代理的方法。在 C# 中事件机制的工作过程如下: (1) 将实际应用中需通过事件机制解决的问题对象注册到相应的事件处理程序上,表示今后当该对象的状态变化时,该对象有权使用它注册的事件处理程序。 (2) 当事件发生时,触发事件的对象代理就会调用该对象所有已注册的事件处理程序。 为了实现上述的“事件机制”, C# 语言创建和使用事件的语言结构描述包括以下步骤:

Page 96: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.2 10.3.2 创建事件和使用事件创建事件和使用事件 1. 为事件定义一个代理 要使用事件,首先必须为它创建一个代理。为事件定义一个代理的语句格式如下: delegate void 代理名 ([触发事件的对象名, 事件参数 ]); 其中: “ 代理名”指的是事件处理方法对应的代理名称,即事件的代理。 事件的代理一般含有两个参数,用于指出所要代理的事件的触发者和该事件的职能: 第一个参数是指触发事件的源,即触发事件的对象 ( 事件发送者 ); 第二个参数是一个类,它包含事件处理方法要使用的数据。 在 .NET框架的 System命名空间内,已有一个专门用于处理事件的代理类 EvenHandler ,它可用于处理各种事件,其原型代码如下: public delegate void EventHandler(Object sender , EventArgs e); 其中第一个参数是发送事件或触发事件的对象;第二个参数是描述关于被触发事件的事件信息,并且没有返回值。如果用户定义的事件不包含附加的信息,则可以直接使用上述System空间下的 EventHandler 代理,而不必再重新定义一个代理。这种做法是 .NET框架中事件处理机制中所使用的方式,即如果开发者准备使用 .NET 框架体系的上述预定义的代理 EvenHandler ,则可以省去为事件创建代理的这一步。当然,用户也可以不使用这种模式的代理,完全可以自定义一个事件的代理,并且也可以具有任意的参数和任意的返回值。 例如:以下代码行为事件创建了一个代理,并表明将要代理的事件是检查赋给的字符,如果是一个特定的某个字符,则驱动事件发生。 delegate void CharEventHandler(Object sender, CharEventArgs e); 此代码定义了一个名为 CharEventHandler 的代理,并需要从 EventArgs 类派生出一个名为 CharEventArgs 的类。

Page 97: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.2 10.3.2 创建事件和使用事件创建事件和使用事件 2. 创建一个包含事件信息的类 事件信息将作为事件参数发送给事件接收者。 在 .NET框架的 System命名空间内,已为用户提供了一个事件参数类 EventArgs( 在上面已提到,它是 System命名空间中事件代理 EventHandler 的第二个参数 ) 。这个类是 .NET框架中所有事件参数的基类,用于存储所需的数据成员 (其中定义的数据是将传递给事件处理方法的数据 ) 。它的原型代码为: public class EventArgs { public static readonly EventArgs Empty=new EventArgs(); public EventArgs() { }; // 构造函数 } 从上述代码可见, EventArgs 类不包含任何实际的内容。如果用户要处理的事件不包含任何附加信息 ( 如“单击按钮”事件 ) ,则可以直接使用这个类来定义事件参数,这种做法是 .NET框架中事件处理机制中所使用的方式。同样,用户也可不必从EventArgs 类派生自己的事件参数类,而可以任意地定义一个事件参数类,或根本不使用事件参数。 EventArgs 类的派生类格式如下:

Page 98: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.2 10.3.2 创建事件和使用事件创建事件和使用事件 public class xxxEventArgs : EventArgs { …..// 数据成员 public xxxEventArgs( 类型名 ) { …..// 通过构造函数给数据成员赋初值 } } xxxEventArgs 类可以取任何名称,但编程习惯上应该以 EventArgs 结尾,表示这种类的特征。 xxxEventArgs 类将包含事件处理方法需要的所有数据,这些数据以后将被传递给事件处理方法。 例如:上述创建的事件代理 CharEventHandler ,它将传递一个 CharEventArgs 类的对象,而 CharEventArgs 可以从EventArgs 类派生而来,代码如下: public class CharEventArgs : EventArgs { public char CurrChar; //字符变量 public CharEventArgs(char CurrChar) // 构造函数 { this.CurrChar=CurrChar; } } 上面给出的代码中定义了一个 CurrChar字符变量,事件处理方法的代码就可以使用这个变量的值;另外还给出了构造函数,当创建一个 CharEventArgs 类的对象时,构造函数将接受一个传递来的字符,并将该字符赋给类中的数据成员。

Page 99: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.2 10.3.2 创建事件和使用事件创建事件和使用事件 3. 创建包含事件成员的类 ( 又称为事件类 ) 上述我们已创建了一个完成“事件过程”的全权代理,现在我们要创建所谓的“事件类”,它包含参与事件的事件成员,以及在条件满足时触发事件的方法代码,即令方法与事件相关联。创建事件类的步骤如下: (1) 定义事件成员 由于“事件过程”是由代理来执行的,所以对于所要发生的事件的定义必须明确指定由哪个“代理”来代理“这个事件”的,所以对某个事件定义的格式中必须含有一个将代理该事件的代理,定义格式如下: event 事件的代理名 事件名 其中: 事件的代理名是指上述第 (1)步中为事件创建的代理。 事件名是要定义的事件对象的名称,它将用于为代理指明处理该事件所要用到的事件处理方法。

Page 100: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.2 10.3.2 创建事件和使用事件创建事件和使用事件 (2) 创建触发事件的方法或属性等成员触发事件的方法或属性等成员用于接收触发事件的触发信息,并通过事件对象调用事件代理,并进而实现调用事件处理方法。 例如:为上述检查特殊字符的事件自定义为一个事件类,代码如下: class CharChecker { char curr_char; // 定义一个由 CharEventHandler 代理的事件成员 CharTest public event CharEventHandler CharTest; //触发事件的属性。接收触发事件的信息并调用相应的处理事件方法 public char Curr_Char { get{return curr_char;} //返回类中数据成员 curr_char 的值 set { if (CharTest!=null) { // 创建 CharEventArgs 类的对象 CharEventArgs myeven=new CharEventArgs (value); // 通过事件成员 CharTest调用事件代理,并由代理调用注册的处理事件的方法 CharTest(this, myeven); curr_char= myeven.CurrChar; } } } }

Page 101: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.2 10.3.2 创建事件和使用事件创建事件和使用事件 在上述事件类中, CharTest 被定义为一个事件成员,所以在 set 属性段中,仅当没有对应的事件处理方法时,这个成员才会为 null;而如果有事件处理方法,则创建一个 CharEventArgs 类的对象,该类将包含事件处理方法需要的所有值。本例中传递给 set 方法的值被传递给 CharEventArgs 类的构造函数 ( 上面已给出 CharEventArgs 类的代码 ) ,这是一个事件处理方法要使用的字符。 CharTest(this, myeven ) 语句用于调用事件代理,这是通过前面定义的事件对象 CharTest 来实现的,其中参数 this 指的是调用事件成员就是 CharTest 事件成员本身;参数 myeven 是 CharEventArgs 类的对象,所以这个代理把事件对象与该事件对应的事件处理方法连在了一起,它们的关系如图 10.2 所示。 curr_char= myeven.CurrChar 语句将 CharEventArgs 类的对象 myeven 中包含的字符赋给数据成员 curr_char ,这条语句是该事件类特有的,如果调用的事件处理方法中修改了数据,则本语句将确保该事件类获得更新后的值,这也正是 set 属性的用途所在。

Page 102: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.2 10.3.2 创建事件和使用事件创建事件和使用事件

事件成员

事件代理

处理事件的方法

图 10.2 事件代理的桥梁作用

Page 103: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.2 10.3.2 创建事件和使用事件创建事件和使用事件 4. 创建事件处理方法 当事件被触发后,将通过该事件的代理来调用处理该事件的事件处理方法。处理事件的方法是用事件代理的格式创建的,其格式如下: void 事件处理方法名 (Object sender, xxxEventArgs argName) { ……// 事件处理方法代码 } 其中: “ 事件处理方法名”是指事件发生时将被调用的方法的名称。 参数“ Object sender” 是指触发事件的对象。 参数“ xxxEventArgs argName” 是一个从EventArgs 类派生的类,它包含事件处理方法要使用的值。 “ 事件处理方法代码”可以是任意代码。如事件是“单击按钮”,则这些代码将在按钮被单击时执行,比如,如果事件是“取消按钮”,则事件处理方法代码将执行取消逻辑;如果是“ OK按钮”,则这些代码将执行一些正常执行的代码。 事件处理方法可以定义在应用类中,也可以专门创建一个包含事件处理方法的类。 例如:仍用上述字符事件来说明,定义一个事件处理方法:如果输入一个字符是 x,则将替换为?,这个方法虽没什么价值,但便于我们理解: static void Change_X(Object source, CharEventArgs e) { if(e.CurrChar=='x' || e.CurrChar=='X') { Console.WriteLine("Change the 'x'!"); e.CurrChar='?'; } }

Page 104: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.2 10.3.2 创建事件和使用事件创建事件和使用事件 5. 将事件处理方法和事件关联起来 最后,我们应将包含事件成员的类和包含事件处理方法的类通过代理关联起来,以便当包含事件成员的类的状态发生变化时,可以准确地通过代理来调用相应的事件处理方法,这一关联过程是在主程序中完成的。步骤如下: (1) 创建事件类的对象要将事件处理方法与相应的事件关联,必须在主程序中事先创建一个事件类的对象。就上述字符事件的例子而言,可以创建一个事件类的对象 chartester : CharChecker chartester=new CharChecker(); (2) 将事件处理方法与事件对象关联将事件处理方法与事件对象关联,就是我们在本节一开始提出的“注册”。可以把一个事件与一个或多个事件处理方法相关联。当把一个事件与多个事件处理方法相关联,也叫做“多播”,具体实现方法与代理的多播一样,可以用 +=运算符来实现。当然也可以使用 -= 运算符删除与一个事件相关联的事件处理方法。实现关联的语句格式如下: 事件类对象名 . 事件类中定义的事件成员 +=new 事件代理名 ( 事件处理方法名列表 ); 例如在字符事件的使用时,建立事件与事件处理方法的关联 (注册 ) 语句可以是: chartester. CharTest +=new CharEventHandler(Change_X);

Page 105: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.2 10.3.2 创建事件和使用事件创建事件和使用事件 6. 组成一个完整的创建事件和使用事件的程序 以下将上述的“字符事件”创建和使用的全过程代码给出,以便读者领会 C# 语言中的“事件机制”实现的应用: using System; delegate void CharEventHandler(Object sender, CharEventArgs e); // 创建一个事件代理 public class CharEventArgs : EventArgs // 创建一个包含事件所需信息 ( 数据 ) 的类 { public char CurrChar; //字符变量 public CharEventArgs(char CurrChar) // 构造函数 { this.CurrChar=CurrChar; } } class CharChecker // 定义事件类 { char curr_char; // 定义一个由 CharEventHandler 事件成员 CharTest public event CharEventHandler CharTest; public char Curr_Char //触发事件的属性。接收触发事件的信息并调用处理事件的方法 { get{return curr_char;} //返回类中数据成员 curr_char 的值 set { if (CharTest!=null) {

Page 106: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.2 10.3.2 创建事件和使用事件创建事件和使用事件 // 创建 CharEventArgs 类的对象 CharEventArgs myeven =new CharEventArgs (value); // 通过事件成员 CharTest调用事件代理,并由代理调用注册的事件处理方法 CharTest(this, myeven); curr_char= myeven.CurrChar; } } } } class AppEvent { static void Main() { CharChecker chartester=new CharChecker(); // 创建事件类 CharChecker 的对象 // 通过事件代理向事件成员注册事件处理方法 chartester. CharTest +=new CharEventHandler(Change_X); // 由事件对象 chartester 通过事件属性 Curr_Char 的 set访问函数来触发事件 chartester.Curr_Char ='a'; // 通过事件属性 Curr_Char 的 get访问函数返回事件处理结果 Console.WriteLine("事件处理结果: {0}", chartester.Curr_Char ); chartester.Curr_Char ='b'; Console.WriteLine("事件处理结果: {0}", chartester.Curr_Char ); chartester.Curr_Char ='x'; Console.WriteLine("{0}", chartester.Curr_Char ); Console.WriteLine(); }

Page 107: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.2 10.3.2 创建事件和使用事件创建事件和使用事件 static void Change_X(Object source, CharEventArgs e) // 事件处理方法 { if(e.CurrChar=='x' || e.CurrChar=='X') { Console.Write("触发的字符是 x,"); Console.Write("把 x替为: "); e.CurrChar='?'; } else Console.Write("触发的字符不是 x,"); } } 程序运行结果: 触发的字符不是 x,事件处理的结果: a 触发的字符不是 x,事件处理的结果: b 触发的字符是 x,把 x替为: ? 以下再举一例以加深对事件的理解。

Page 108: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.2 10.3.2 创建事件和使用事件创建事件和使用事件 【例 10.14】一个 E-mail 事件的处理程序。当收到一个 E-mail消息时,程序就把这条消息转发给传真机或直接显示在屏幕上。 在此例中,我们根据事件发生的过程,先创建一个 E-mail管理器,即一个 MailManager 类,该类的将包括一个事件成员 MailMag 、一个事件代理成员 MailMagEventHandler 和一个接收发来的 E-mail信息的触发事件的方法。还要建一个包含事件处理方法的类 Fax。整个程序完成的功能是:当MailManager管理器收到 E-mail消息时,它就触发 MailMag 事件,并把 E-mail消息转发给注册到该事件上的 Fax对象。程序结构如图 10.3 所示。

Page 109: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.2 10.3.2 创建事件和使用事件创建事件和使用事件

注册事件

注册事件

E_mail

E-mail 管理器

(MailManager 类)

Fax 类

类 Fax 类

图 10.3 程序结构示意图

Page 110: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.3 10.3.3 多播事件多播事件 事件可以多播,这一特性可以使多个对象响应事件信息;还可以通过多点传送 (multicasting) 为同一个事件指定多个事件处理方法( 新加入的处理程序必须有同样的格式 ) 。要加入其他的事件处理程序,可以使用上节中介绍的 += 运算符。下面举例说明:

Page 111: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.3 10.3.3 多播事件多播事件 【例 10.15】使用多个事件处理程序将前面的字符事件修改如下。 using System; delegate void CharEventHandler(Object sender, CharEventArgs e); public class CharEventArgs : EventArgs { public char CurrChar; //字符变量 public CharEventArgs(char CurrChar) // 构造函数 { this.CurrChar=CurrChar; } } class CharChecker // 定义事件类 { char curr_char; // 定义一个由 CharEventHandler 事件成员 CharTest public event CharEventHandler CharTest; public char Curr_Char//触发事件的属性。接收触发事件的信息并调用处理事件的方法 { get{return curr_char;} //返回类中数据成员 curr_char 的值 set { if (CharTest!=null) {

Page 112: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.3 10.3.3 多播事件多播事件 CharEventArgs args=new CharEventArgs (value);// 创建 CharEventArgs 类的对象 // 通过事件成员 CharTest调用事件代理,并由代理调用注册的事件处理方法 CharTest(this, args); curr_char=args.CurrChar; } } } } class AppEvent { static void Main() { CharChecker chartester=new CharChecker();// 创建事件类 CharChecker 的对象 // 通过事件代理向事件成员注册事件处理方法 chartester. CharTest +=new CharEventHandler(Change_X); // 将新的事件处理方法加入到事件处理调用列表中,当赋给字母时,两个事件处理方法 都执行 chartester. CharTest +=new CharEventHandler(Change_Y); // 由事件对象 chartester 通过事件属性 Curr_Char 的 set访问函数来触发事件 chartester.Curr_Char ='a'; // 通过事件属性 Curr_Char 的 get访问函数返回事件处理结果 Console.WriteLine("事件处理结果: {0}", chartester.Curr_Char ); chartester.Curr_Char ='x'; Console.WriteLine ("把 x替为: {0}" , chartester.Curr_Char); chartester.Curr_Char ='b'; Console.WriteLine("事件处理结果: {0}", chartester.Curr_Char ); chartester.Curr_Char ='y'; Console.WriteLine ("把 y替为: {0}" , chartester.Curr_Char); }

Page 113: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.3 10.3.3 多播事件多播事件 static void Change_X(Object source, CharEventArgs e) // 事件处理方法 { if(e.CurrChar=='x' || e.CurrChar=='X') { Console.Write("触发的字符是 x,"); e.CurrChar='?'; } else Console.Write("触发的字符不是 x,"); } // 新的事件处理方法,其格式与要求的相同,返回类型为 void static void Change_Y(Object source, CharEventArgs e) { if(e.CurrChar=='y' || e.CurrChar=='Y') { Console.Write("触发的字符是 y,"); e.CurrChar='#'; } else Console.Write("触发的字符不是 y,"); } }

Page 114: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

10.3.3 10.3.3 多播事件多播事件 程序运行结果: 触发的字符不是 x, 触发的字符不是 y, 事件处理结果: a 触发的字符是 x, 触发的字符不是 y, 把 x替为: ? 触发的字符不是 x, 触发的字符不是 y, 事件处理结果: b 触发的字符不是 x, 触发的字符是 y, 事件处理结果: #

Page 115: 第 10 章  接口、代理和事件

http://www.wenyuan.com.cn/webnew/

Q & A?Q & A?

Thanks!Thanks!