无私分享(C#高级编程第6版doc)第07章 委托和事件费.doc_第1页
无私分享(C#高级编程第6版doc)第07章 委托和事件费.doc_第2页
无私分享(C#高级编程第6版doc)第07章 委托和事件费.doc_第3页
无私分享(C#高级编程第6版doc)第07章 委托和事件费.doc_第4页
无私分享(C#高级编程第6版doc)第07章 委托和事件费.doc_第5页
已阅读5页,还剩18页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

目 录第7章 委托和事件27.1 委托27.1.1 在C#中声明委托27.1.2 在C#中使用委托37.1.3 简单的委托示例67.1.4 BubbleSorter示例77.1.5 多播委托107.1.6 匿名方法137.1.7 l表达式147.1.8 协变和抗变167.2 事件177.2.1 从接收器的角度讨论事件177.2.2 生成事件197.3 小结23第7章 委托和事件回调(callback)函数是Windows编程的一个重要部分。如果您具备C或C+编程背景,应该就曾在许多Windows API中使用过回调。Visual Basic添加了AddressOf关键字后,开发人员就可以利用以前一度受到限制的API了。回调函数实际上是方法调用的指针,也称为函数指针,是一个非常强大的编程特性。.NET以委托的形式实现了函数指针的概念。它们的特殊之处是,与C函数指针不同,.NET委托是类型安全的。这说明,C中的函数指针只不过是一个指向存储单元的指针,我们无法说出这个指针实际指向什么,像参数和返回类型等就更无从知晓了。如本章所述,.NET把委托作为一种类型安全的操作。本章后面将学习.NET如何将委托用作实现事件的方式。本章的主要内容如下:委托匿名方法 表达式事件7.1 委托当要把方法传送给其他方法时,需要使用委托。要了解它们的含义,可以看看下面的代码:int i = int.Parse(99);我们习惯于把数据作为参数传递给方法,如上面的例子所示。所以,给方法传送另一个方法听起来有点奇怪。而有时某个方法执行的操作并不是针对数据进行的,而是要对另一个方法进行操作,这就比较复杂了。在编译时我们不知道第二个方法是什么,这个信息只能在运行时得到,所以需要把第二个方法作为参数传递给第一个方法,这听起来很令人迷惑,下面用几个示例来说明:启动线程- 在C#中,可以告诉计算机并行运行某些新的执行序列。这种序列就称为线程,在基类System.Threading.Thread的一个实例上使用方法Start(),就可以开始执行一个线程。如果要告诉计算机开始一个新的执行序列,就必须说明要在哪里执行该序列。必须为计算机提供开始执行的方法的细节,即Thread类的构造函数必须带有一个参数,该参数定义了要由线程调用的方法。通用库类- 有许多库包含执行各种标准任务的代码。这些库通常可以自我包含。这样在编写库时,就会知道任务该如何执行。但是有时在任务中还包含子任务,只有使用该库的客户机代码才知道如何执行这些子任务。例如编写一个类,它带有一个对象数组,并把它们按升序排列。但是,排序的部分过程会涉及到重复使用数组中的两个对象,比较它们,看看哪一个应放在前面。如果要编写的类必须能给任何对象数组排序,就无法提前告诉计算机应如何比较对象。处理类中对象数组的客户机代码也必须告诉类如何比较要排序的对象。换言之,客户机代码必须给类传递某个可以进行这种比较的合适方法的细节。事件- 一般是通知代码发生了什么事件。GUI编程主要是处理事件。在发生事件时,运行库需要知道应执行哪个方法。这就需要把处理事件的方法传送为委托的一个参数。这些将在本章后面讨论。在C和C+中,只能提取函数的地址,并传送为一个参数。C是没有类型安全性的。可以把任何函数传送给需要函数指针的方法。这种直接的方法会导致一些问题,例如类型的安全性,在进行面向对象编程时,方法很少是孤立存在的,在调用前,通常需要与类实例相关联。而这种方法并没有考虑到这个问题。所以.NET Framework在语法上不允许使用这种直接的方法。如果要传递方法,就必须把方法的细节封装在一种新类型的对象中,即委托。委托只是一种特殊的对象类型,其特殊之处在于,我们以前定义的所有对象都包含数据,而委托包含的只是方法的地址。7.1.1 在C#中声明委托在C#中使用一个类时,分两个阶段。首先需要定义这个类,即告诉编译器这个类由什么字段和方法组成。然后(除非只使用静态方法)实例化类的一个对象。使用委托时,也需要经过这两个步骤。首先定义要使用的委托,对于委托,定义它就是告诉编译器这种类型的委托代表了哪种类型的方法,然后创建该委托的一个或多个实例。编译器在后台将创建表示该委托的一个类。定义委托的语法如下:delegate void IntMethodInvoker(int x);在这个示例中,定义了一个委托IntMethodInvoker,并指定该委托的每个实例都包含一个方法的细节,该方法带有一个int参数,并返回void。理解委托的一个要点是它们的类型安全性非常高。在定义委托时,必须给出它所代表的方法签名和返回类型等全部细节。提示:理解委托的一种好方式是把委托当作给方法签名和返回类型指定名称。假定要定义一个委托TwoLongsOp,该委托代表的方法有两个long型参数,返回类型为double。可以编写如下代码:delegate double TwoLongsOp(long first, long second);或者定义一个委托,它代表的方法不带参数,返回一个string型的值,则可以编写如下代码:delegate string GetAString();其语法类似于方法的定义,但没有方法体,定义的前面要加上关键字delegate。因为定义委托基本上是定义一个新类,所以可以在定义类的任何地方定义委托,既可以在另一个类的内部定义,也可以在任何类的外部定义,还可以在命名空间中把委托定义为顶层对象。根据定义的可见性,可以在委托定义上添加一般的访问修饰符:public、private、protected等:public delegate string GetAString();注意:实际上,定义一个委托是指定义一个新类。委托实现为派生自基类System. Multicast Delegate的类,System.MulticastDelegate又派生自基类System.Delegate。C#编译器知道这个类,会使用其委托语法,因此我们不需要了解这个类的具体执行情况,这是C#与基类共同合作,使编程更易完成的另一个示例。定义好委托后,就可以创建它的一个实例,以存储特定方法的细节。注意:此处,在术语方面有一个问题。类有两个不同的术语:类表示较广义的定义,对象表示类的实例。但委托只有一个术语。在创建委托的实例时,所创建的委托的实例仍称为委托。必须从上下文中确定委托的确切含义。7.1.2 在C#中使用委托下面的代码段说明了如何使用委托。这是在int上调用ToString()方法的一种相当冗长的方式:private delegate string GetAString();static void Main()int x = 40;GetAString firstStringMethod = new GetAString(x.ToString);Console.WriteLine(String is 0 + firstStringMethod();/ With firstStringMethod initialized to x.ToString(), / the above statement is equivalent to saying / Console.WriteLine(String is 0 + x.ToString(); 在这段代码中,实例化了类型为GetAString的一个委托,并对它进行初始化,使它引用整型变量x的ToString()方法。在C#中,委托在语法上总是带有一个参数的构造函数,这个参数就是委托引用的方法。这个方法必须匹配最初定义委托时的签名。所以在这个示例中,如果用不带参数、返回一个字符串的方法来初始化firstStringMethod变量,就会产生一个编译错误。注意,int.ToString()是一个实例方法(不是静态方法),所以需要指定实例(x)和方法名来正确初始化委托。下一行代码使用这个委托来显示字符串。在任何代码中,都应提供委托实例的名称,后面的括号中应包含调用该委托中的方法时使用的参数。所以在上面的代码中,Console.WriteLine()语句完全等价于注释语句中的代码行。实际上,给委托实例提供括号与调用委托类的Invoke()方法完全相同。firstStringMethod是委托类型的一个变量,所以C#编译器会用firstStringMethod.Invoke()代替firstStringMethod()。firstStringMethod();firstStringMethod. Invoke();C# 2.0使用委托推断扩展了委托的语法。为了减少输入量,只要需要委托实例,就可以只传送地址的名称。这称为委托推断。只要编译器可以把委托实例解析为特定的类型,这个C#特性就是有效的。下面的示例用GetAString委托的一个新实例初始化了GetAString类型的变量firstStringMethod:GetAString firstStringMethod = new GetAString(x.ToString);只要用变量x把方法名传送给变量firstStringMethod,就可以编写出作用相同的代码:GetAString firstStringMethod = x.ToString;C#编译器创建的代码是一样的。编译器会用firstStringMethod检测需要的委托类型,因此创建GetAString委托类型的一个实例,用对象x把方法的地址传送给构造函数。注意:不能调用x.ToString()方法,把它传送给委托变量。调用x.ToString()方法会返回一个不能赋予委托变量的字符串对象。只能把方法的地址赋予委托变量。委托推断可以在需要委托实例的任何地方使用。委托推断也可以用于事件,因为事件基于委托(参见本章后面的内容)。委托的一个特征是它们的类型是安全的,可以确保被调用的方法签名是正确的。但有趣的是,它们不关心在什么类型的对象上调用该方法,甚至不考虑该方法是静态方法,还是实例方法。提示:给定委托的实例可以表示任何类型的任何对象上的实例方法或静态方法- 只要方法的签名匹配于委托的签名即可。为了说明这一点,我们扩展上面的代码,让它使用firstStringMethod委托在另一个对象上调用其他两个方法,其中一个是实例方法,另一个是静态方法。为此,再次使用本章前面定义的Currency结构。Currency结构有自己的ToString()重载方法和一个与GetCurrencyUnit()的签名相同的静态方法,这样,就可以用同一个委托变量调用这些方法了: struct Currencypublic uint Dollars;public ushort Cents; public Currency(uint dollars, ushort cents)this.Dollars = dollars;this.Cents = cents;public override string ToString()return string.Format($0.1,-2:00, Dollars,Cents);public static string GetCurrencyUnit()return Dollar;public static explicit operator Currency (float value)checkeduint dollars =(uint)value;ushort cents =(ushort)(value-dollars)*100);return new Currency(dollars,cents);public static implicit operator float (Currency value)return value.Dollars + (value.Cents/100.0f);public static implicit operator Currency (uint value)return new Currency(value, 0);public static implicit operator uint (Currency value)return value.Dollars;下面就可以使用GetAString 实例,代码如下所示: private delegate string GetAString(); static void Main()int x = 40;GetAString firstStringMethod = x.ToString;Console.WriteLine(String is 0 + firstStringMethod(); Currency balance = new Currency(34, 50);/ firstStringMethod references an instance methodfirstStringMethod = balance.ToString;Console.WriteLine(String is 0 + firstStringMethod();/ firstStringMethod references a static methodfirstStringMethod = new GetAString(Currency.GetCurrencyUnit);Console.WriteLine(String is 0 + firstStringMethod();这段代码说明了如何通过委托来调用方法,然后重新给委托指定在类的不同实例上执行的不同方法,甚至可以指定静态方法,或者在类的不同类型的实例上执行的方法,只要每个方法的签名匹配委托定义即可。运行应用程序,会得到委托引用的不同方法的结果:String is 40String is $34.50String is Dollar但是,我们还没有说明把一个委托传递给另一个方法的具体过程,也没有给出任何有用的结果。调用int和Currency对象的ToString()的方法要比使用委托直观得多!在真正领会到委托的用处前,需要用一个相当复杂的示例来说明委托的本质。下面就是两个委托的示例。第一个示例仅使用委托来调用两个不同的操作,说明了如何把委托传递给方法,如何使用委托数组,但这仍没有很好地说明:没有委托,就不能完成很多工作。第二个示例就复杂得多了,它有一个类BubbleSorter,执行一个方法,按照升序排列一个对象数组,这个类没有委托是很难编写出来的。7.1.3 简单的委托示例在这个示例中,定义一个类MathsOperations,它有两个静态方法,对double类型的值执行两个操作,然后使用该委托调用这些方法。这个数学类如下所示: class MathsOperationspublic static double MultiplyByTwo(double value)return value*2; public static double Square(double value)return value*value;下面调用这些方法:using System;namespace Wrox.ProCSharp.Delegatesdelegate double DoubleOp(double x); class Programstatic void Main()DoubleOp operations = MathsOperations.MultiplyByTwo,MathsOperations.Square; for (int i=0 ; ioperations.Length ; i+)Console.WriteLine(Using operations0:, i);ProcessAndDisplayNumber(operationsi, 2.0);ProcessAndDisplayNumber(operationsi, 7.94);ProcessAndDisplayNumber(operationsi, 1.414);Console.WriteLine(); static void ProcessAndDisplayNumber(DoubleOp action, double value)double result = action(value);Console.WriteLine(Value is 0, result of operation is 1, value, result);在这段代码中,实例化了一个委托数组DoubleOp (记住,一旦定义了委托类,就可以实例化它的实例,就像处理一般的类那样- 所以把一些委托的实例放在数组中是可以的)。该数组的每个元素都初始化为由MathsOperations类执行的不同操作。然后循环这个数组,把每个操作应用到3个不同的值上。这说明了使用委托的一种方式- 把方法组合到一个数组中,这样就可以在循环中调用不同的方法了。这段代码的关键一行是把委托传递给ProcessAndDisplayNumber()方法,例如: ProcessAndDisplayNumber(operationsi, 2.0);其中传递了委托名,但不带任何参数,假定operationsi是一个委托,其语法是:operationsi表示这个委托。换言之,就是委托代表的方法。operationsi(2.0)表示调用这个方法,参数放在括号中。ProcessAndDisplayNumber()方法定义为把一个委托作为其第一个参数: static void ProcessAndDisplayNumber(DoubleOp action, double value)在这个方法中,调用: double result = action(value);这实际上是调用action委托实例封装的方法,其返回结果存储在result中。运行这个示例,得到如下所示的结果:SimpleDelegateUsing operations0:Value is 2, result of operation is 4Value is 7.94, result of operation is 15.88Value is 1.414, result of operation is 2.828Using operations1:Value is 2, result of operation is 4Value is 7.94, result of operation is 63.0436Value is 1.414, result of operation is 1.9993967.1.4 BubbleSorter示例下面的示例将说明委托的用途。我们要编写一个类BubbleSorter,它执行一个静态方法Sort(),这个方法的第一个参数是一个对象数组,把该数组按照升序重新排列。换言之,假定传递的是int数组:0, 5, 6, 2, 1,则返回的结果应是0, 1, 2, 5, 6。冒泡排序算法非常著名,是一种排序的简单方法。它适合于一小组数字,因为对于大量的数字(超过10个),还有更高效的算法。冒泡排序算法重复遍历数组,比较每一对数字,按照需要交换它们的位置,把最大的数字逐步移动到数组的最后。对于给int排序,进行冒泡排序的方法如下所示: for (int i = 0; i sortArray.Length; i+)for (int j = i + 1; j sortArray.Length; j+)if (sortArrayj sortArrayi) / problem with this testint temp = sortArrayi; / swap ith and jth entriessortArrayi = sortArrayj;sortArrayj = temp;它非常适合于int,但我们希望Sort()方法能给任何对象排序。换言之,如果某段客户机代码包含Currency结构数组或其他类和结构,就需要对该数组排序。这样,上面代码中的if(sortArrayj sortArrayi)就有问题了,因为它需要比较数组中的两个对象,看看哪一个更大。可以对int进行这样的比较,但如何对直到运行才知道或确定的新类进行比较?答案是客户机代码知道类在委托中传递的是什么方法,封装这个方法就可以进行比较。定义如下的委托: delegate bool Comparison(object x, object y);给Sort方法指定下述签名: static public void Sort(object sortArray, Comparison comparison)这个方法的文档说明强调,comparison必须表示一个静态方法,该方法带有两个参数,如果第二个参数的值大于第一个参数(换言之,它应放在数组中靠后的位置),就返回true。设置完毕后,下面定义类BubbleSorter: class BubbleSorterstatic public void Sort(object sortArray, Comparison comparison)for (int i=0 ; isortArray.Length ; i+)for (int j=i+1 ; jsortArray.Length ; j+)if (comparison(sortArrayj, sortArrayi)object temp = sortArrayi;sortArrayi = sortArrayj;sortArrayj = temp;为了使用这个类,需要定义另一个类,建立要排序的数组。在本例中,假定Mortimer Phones移动电话公司有一个员工列表,要对照他们的薪水进行排序。每个员工分别由类Employee的一个实例表示,如下所示: class Employeeprivate string name;private decimal salary; public Employee(string name, decimal salary) = name;this.salary = salary; public override string ToString()return string.Format(0, 1:C, name, salary); public static bool CompareSalary(object x, object y)Employee e1 = (Employee) x;Employee e2 = (Employee) y;return (e1.salary param += mid;param += and this was added to the string.;return param;Console.WriteLine(anonDel(Start of string);运算符=的左边列出了匿名方法需要的参数。这有几种编写方式。例如,如果需要在示例代码中把一个字符串参数定义为委托类型,一种方式是在括号中定义类型和变量名:(string param)在 表达式中,不需要给声明添加变量类型,因为编译器知道该类型:(param)如果只有一个参数,就可以删除括号:param表达式的右边列出了实现代码。在示例程序中,实现代码放在花括号中,类似于前面的匿名方法:param += mid;param += and this was added to the string.;return param;如果实现代码只有一行,也可以删除花括号和return语句,因为编译器会自动添加该语句。例如,在下面的委托中,需要一个int参数,返回一个bool值:public delegate bool Predicate(int obj)可以声明一个委托变量,并指定一个 表达式。在 表达式中,左边定义了变量x。这个变量的类型自动设置为int,因为这是通过委托定义的。实现代码返回比较x5的布尔结果。如果x大于5,就返回true,否则返回false。Predicate p1 = x = x 5;可以把这个 表达式传送给需要谓词参数的方法:list.FindAll(x = x 5);这里列出了相同的 表达式,但把变量x的类型定义为int,没有使用变量类型推断功能,还在实现代码中添加了return语句:list.FindAll(int x) = return x 5; );如果使用以前的语法,可以通过匿名方法完成相同的功能:list.FindAll(delegate(int x) return x 5; );通过所有这些改变,C#编译器就创建出了相同的IL代码。修改前面的SimpleDelegate示例,使用 表达式,可以删除类MathOperations。Main()方法现在如下所示:static void Main()DoubleOp multByTwo = val = val * 2;DoubleOp square = val = val * val;DoubleOp operations = multByTwo, square;for (int i=0 ; i operations.Length ; i+)Console.WriteLine(Using operations0:, i);ProcessAndDisplayNumber(operationsi, 2.0);ProcessAndDisplayNumber(operationsi, 7.94);ProcessAndDisplayNumber(operationsi, 1.414);Console.WriteLine();运行这个版本,可以得到与上例相同的结果。但优点是它删除了类。提示:表达式可以用于委托是类型的任意地方。类型是Expression或Expression时,也可以使用 表达式。此时编译器会创建一个表达式树,详见第11章。7.1.8 协变和抗变委托调用的方法不需要与委托声明定义的类型相同。因此可能出现协变和抗变。1. 返回类型协变方法的返回类型可以派生于委托定义的类型。在下面的示例中,委托MyDelegate定义为返回DelegateReturn类型。赋予委托实例d1的方法返回DelegateReturn2类型,DelegateReturn2派生自DelegateReturn,因此满足了委托的需求。这称为返回类型协变。public class DelegateReturnpublic class DelegateReturn2 : DelegateReturnpublic delegate DelegateReturn MyDelegate1();class Programstatic void Main()MyDelegate1 d1 = Method1;d1();static DelegateReturn2 Method1()DelegateReturn2 d2 = new DelegateReturn2();return d2; 2. 参数类型抗变术语参数类型抗变表示,委托定义的参数可能不同于委托调用的方法。这里是返回

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论