一章面向对象程序设计.ppt
第10章 面向对象程序设计,内容介绍,10.1 面向对象技术的基本概念 10.2 类的定义和对象10.3 构造函数和析构函数10.4 类的属性10.5 this引用10.6 类的静态数据成员和静态方法10.7 类的继承性10.8 类的多态性10.9 抽象类和抽象方法10.10委托10.11事件10.12索引指示器10.13接口10.14运算符重载10.15命名空间,学习目标,掌握面向对象的程序设计思想;掌握类的封装性、继承性和多态性的实现方法;掌握接口、委托与事件的使用方法;掌握运算符重载的方法。,10.1面向对象技术的基本概念,结构化程序设计的缺点:用此类语言设计的程序结构清晰,使用方便,但是它将数据和对数据的处理过程分开,一旦数据结构改变,其相关处理过程必须修改,程序可重用性差,这给软件的调试和维护带来了困难。20世纪九十年代,人们提出了面向对象的程序设计思想(OOP,Object Oriented Programming)。面向对象程序设计是C#编程的核心技术,使得程序的设计更加合理,而且易于维护。OOP技术由5个最基本的概念组成:类(class)、对象(object)、方法(method)、消息(message)和继承(inheritance)。,1.类,是对一组具有相似特性的客观事物的抽象,是对事物的特性和功能的描述,这一点体现了类的封装性。封装是一种程序设计机制,它绑定代码及其操作的数据,并使它们不受外界干涉和误用的影响,从而保证安全性。类与传统的模块概念是不同的。模块的层次结构在逻辑上体现了模块间的功能概括关系,而类的层次结构在逻辑上体现的是类的继承关系,处于上层的类称为父类或基类,处于下层的类称为子类或派生类。,2.对象,对象是类类型的变量,是类的实例。例如:35英寸康佳液晶彩电;42英寸康佳液晶彩电 一个类可以定义多个对象,对象的属性值可以不同,但它们的操作功能是相同的。,3.方法,方法是指实现对象所具有的操作功能的代码。每个对象一般包含若干种方法,每个方法有方法名和对应的一组代码。方法体现了对象的一种行为能力,程序通过对象的方法,去获取或修改对象的属性。,4.消息,客观世界是有各种对象组成的,对象之间存在着联系,对象之间的联系是通过消息激活机制实现的。例如:张先生拿着电视遥控器在看电视,张先生是对象,电视机是对象,电视遥控器也是对象。当张先生按下遥控器的提高声音的按钮时,张先生就和遥控器之间建立了联系,即张先生向遥控器发出了要求提高声音的消息;遥控器对按钮进行判断(遥控器响应该消息),然后向电视机发出调高声音的信号,此时,遥控器和电视机之间也建立了联系,即遥控器向电视机发出了要求提高声音的消息;电视机接收到信号后经过判断(电视机响应该消息),提高了音量。从这个例子中可以看出,消息由三部分组成:发送对象、接受对象和消息的内容。在OOP系统中,向一个对象发送消息,就是调用那个对象的某个功能(函数),消息的实质是调用对象的成员函数。同一个对象可以接受不同形式的多个消息,产生不同的响应。同一个消息可以发送给不同的对象,得到的响应可能是不同的,这一点体现了类的多态性,多态性是OOP的主要特征之一。同时从该例中可以看出,消息也可以传递。,5.继承,继承指的是一个子类可以从现有的父类中派生出来,子类除了继承父类的特性和功能以外,还可以增加新的特性和功能。继承描述的是类间的一种层次关系,继承性是OOP的主要特征之一。继承的一个重要作用是实现代码重用,节省程序开发的时间。程序员可以利用现有的类,按照需求修改该类,以得到一个满足要求的新类。例如程序员可以修改系统预定义的按钮控件,使它的外观呈现出椭圆形。程序员可以重新定义自定义类中从object类中继承来的Equals方法,实现自定义对象的相同判断。,10.2 类的定义和对象,1.类的定义类是一种用户自定义的数据类型。定义格式为:class 类名 访问权限修饰符 其他修饰符 类成员定义;常用的访问权限有三种,分别为:private(私有)、public(公有)和protected(保护)其他修饰符包括:static、readonly、const、virtual、override、sealed、abstract和new。readonly表示只读修饰符,其后只能声明数据成员,readonly型数据成员只能在声明时或构造函数中初始化,其它地方不能修改其值。const表示常量修饰符,其后只能声明数据成员,且只能在声明时初始化,其它地方不能修改其值。如果成员声明前没有访问权限关键字,则默认为private访问权限。,10.2 类的定义和对象,2.对象对象是类的实例(instance),即类的变量。缺省定义格式为:类名 对象名=new 类名();定义对象时,才会为对象的每一个实例数据成员在堆中依次分配存储空间。通过对象,可以访问它的公有成员。,结构体与类的差异,结构体变量是值类型,而类变量(对象)是引用类型。值类型在栈上分配空间,引用类型在堆上分配空间,所以,结构体变量相互赋值是相同数据存储在不同的栈空间中,引用类型相互赋值是两个对象引用同一个堆空间数据。结构体只能从接口继承,不能从结构体继承;类则支持继承和派生。结构体中不能初始化数据成员;类则可以初始化数据成员。系统不给结构体提供默认构造函数,但用户可以显式定义造函数,结构体不提供析构函数;类则支持构造函数和析构函数。结构体没有 abstract、sealed和protected 修饰符。在表现抽象和多层次的类型时,建议采用类的方法;如果只是存储一些数据和少量逻辑时,建议使用结构实现。,【例10-1】定义带有身份证和姓名字段的人员类,并提供接口函数计算年龄。using System;namespace 教材_类和对象 class Program static void Main(string args)Person p1=new Person();p1.SetNameId(王宏,320402196312091234);p1.Display();p1.name=王力宏;/p1.id=320402197908231432;错误,不能通过对象访问其私有成员 p1.Display();Console.ReadKey();,public class Person/声明字段private string id;/可以赋初值,相当于缺省值;不赋初值,则默认为NULL public string name;/可以赋初值,相当于缺省值;不赋初值,则默认为NULL public void Display()/可以在成员函数中访问所有数据成员 DateTime date1=DateTime.Now;/获取计算机的当前时间 int nowy=date1.Year;string by=id.Substring(6,4);int byi=int.Parse(by);int age=nowy-byi;Console.WriteLine(身份证号:0,姓名:1,年龄:2,id,name,age);public void SetNameId(string pname,string pid)name=pname;id=pid;,10.3 构造函数和析构函数,1.构造函数构造函数是一种特殊的成员函数,它的函数名与类名相同,无返回类型。构造函数是在定义对象时自动调用的。构造函数与9.5节的普通函数一样,也可以进行重载,以实现对象的多种初始化方法。2.析构函数对象和其他变量一样,都有生命周期;生命周期结束时,这些变量和对象就要被撤销。对象撤销时,系统自动调用其析构函数,并释放其占用的堆空间。析构函数的名字为类名,无参数,无返回类型,也无任何修饰符。析构函数不能重载。,10.4 类的属性,如何修改对象的private字段呢?除了可以通过public接口函数,设置和读取private字段的方法外,C#还提供了“属性”的方法进行private字段的设置和读取。“属性”总是和某个或某些字段相关联。“属性”本质上属于类的成员函数,提供set和get访问器,用于对其关联字段进行设置和读取,同时在set和get访问器中,可以根据程序逻辑,限制用户的设置和读取操作。“属性”与字段的区别:定义对象时,系统为对象的实例字段分配存储空间,属性不会分配存储空间,属性本质上是方法。字段用小写字母定义,属性以大写字母定义。,属性定义格式:,修饰符 数据类型名 属性名 set/设置关联字段的值 get/获取关联字段的值 数据类型名应与属性关联字段的类型名相同。“属性”分为只读属性、只写属性和读/写属性。只读属性只带get访问器,只写属性只带set访问器,读/写属性带get访问器和set访问器。例如:DateTime的Now属性是只读属性 注意:一个属性的get访问器和set访问器必须在属性的声明中一次性定义,不能分开定义。为了简化代码,C#3.5提供了自动属性的概念,即类不需要显式提供关联字段,而是由编译器自动提供私有关联字段。利用类的属性,可以减少修改字段的公有接口函数的定义。,10.5 this引用,系统为每一个实例方法,提供了一个隐含的this参数,其引用调用该方法的对象,在方法内部,通过this,确定数据成员的归属问题。“this”是一个const型变量,只能使用,不能改变其值。当方法内的临时变量与数据成员同名时,需要在同名数据成员前显式加上this。,10.6 类的静态数据成员和静态方法,1.类的静态数据成员“静态数据成员”:“static”修饰符在定义对象时,要对其每一个实例成员分配存储空间。同一个类的不同对象,其实例数据成员占用不同的存储空间,访问方法为“对象实例数据成员”。类的静态数据成员是该类的所有对象共享的,其访问方法为“类静态数据成员”。2.类的静态方法在定义类的方法时,若在其声明前加“static”修饰符,则该方法为“静态方法”,不加“static”修饰符的方法为实例方法。静态方法只能访问该静态方法所在类的静态数据成员和静态方法。实例方法可以访问所有成员。如:System.Array的Sort、Reverse、Find等3.静态方法不含有this引用,所以通常要将实例对象作为参数传递到static方法中,实例方法中含有this引用,其所获取的实例成员值是该对象特有的。,【例10-5】用静态方法和静态数据成员实现对象个数的统计和显示。using System;namespace 教材_静态成员函数 class Program static void Main(string args)string output;Person p1=new Person();p1.Id=320402198412302323;p1.Name=张飞;p1.Sex=男;p1.Display();Person p2=new Person(320403198712121234,王军霞,女);p2.Display();Person.ReportCount();/只能通过类名访问静态成员 Console.ReadKey();,public class Person private string id;/不赋初值,则默认为NULL private string name;/相当于类的全局变量,可以通过类名使用,每次增加一个对象,总人数加1 public static int totalcount;public string Id get return id;set if(value.Length!=18)return;id=value;public string Name get return name;set name=value;public string Sex set;get;public Person()/每次增加一个对象,人数加1 id=未知;name=未知;Sex=未知;totalcount+;,public Person(string pid,string pname,string psex)/每次增加一个对象,人数加1 id=pid;name=pname;Sex=psex;totalcount+;Person()/每次结束一个对象,人数减1 totalcount-;Console.WriteLine(name+使用完毕,+还有+totalcount+人在使用);Console.ReadKey();public static void ReportCount()/在static函数内,只能使用静态成员;Console.WriteLine(目前总人数为:0,totalcount);public void Display()Console.WriteLine(身份证号:0,姓名:1,性别:2.,id,name,Sex);,10.7 类的继承性,类的三大特性是封装性、继承性和多态性。继承性指的是可以从一个或多个基类派生出一个新类,新类继承了基类的非私有成员,同时,新类又可以根据需要定义新的成员或重写从基类处继承的成员。派生类定义格式为:Class 派生类名:基类名 派生类成员定义;,关于C#中的继承,有以下几点说明:C#只支持单一继承,即只能直接从一个基类处继承。若需要有多个直接基类(多重继承),采用接口的方法实现。继承是可传递的。如果从A派生出B,又从B派生出C,则C既继承了B的成员,又继承了A的成员。由于所有类的根基类是object类,所以,所有类都包含从object类继承来的Equals、GetType、ToString公有方法。基类的构造函数和析构函数都不能被继承,其它成员都可以被继承。在派生类中,可以直接使用基类的protected和public成员,但不能直接使用基类的private成员,可以通过基类的public成员函数访问基类private成员。在声明派生类构造函数时,必须正确调用基类构造函数,若不显式调用基类构造函数,则按基类的缺省构造函数调用。若基类不提供缺省构造函数,则编译错误。在派生类中,可以采用new修饰符,重写基类的非virtual成员函数,以达到在派生类中屏蔽基类中非virtual成员函数的作用。在派生类中,可以采用override修饰符,覆盖基类的virtual成员函数,以实现类的多态性。派生类对象可以赋给基类对象。成员查找路径:通过派生对象调用public成员时,首先在派生类中查找,若找不到,再到直接基类中查找,若还不存在,则继续沿着继承层次向更上一层的基类中查找,直到查找到为止。,【例10-6】用继承实现学生、工作人员和一般人员的定义和使用。using System;namespace 教材_继承 class Program static void Main(string args)Person p1=new Person(320402197812121234,小刘,男);p1.Display();/调用基类的Display()方法 Console.WriteLine();Student s1=new Student(320402199308311423,汪兰,女,大一);s1.Display();/调用派生类的Display()方法 s1.Name=马援;/修改从基类中继承的公有属性值 s1.UpdateGrade(大二);/调用派生类新增的方法,修改其新增字段sclass s1.Display();/调用派生类的Display()方法 worker w1=new worker(320402196305011423,蒋大为,女,9800);w1.Display();/调用派生类的Display()方法 w1.Wage=9000;/修改派生类的新增属性Wage w1.Sex=男;w1.Display();/调用派生类的Display()方法 Console.ReadKey();,public class Person private string id;public string name;public string Sex/定义自动属性,编译器自动提供关联私有字段 get;set;public string Name get return name;set name=value;public void Display()Console.Write(身份证号:0,姓名:1,性别为:2,id,name,Sex);public Person(string pid,string pname,string psex)name=pname;id=pid;Sex=psex;,class Student:Person string sgrade;/增加私有字段年级 public string Sgrade get return sgrade;set sgrade=value;public void UpdateGrade(string pgrade)sgrade=pgrade;public Student(string pid,string pname,string psex,string pgrade):base(pid,pname,psex)/base为关键字,派生类的构造函数中必须提供基类构造函数的调用,否则,按缺省方法调用 sgrade=pgrade;new public void Display()/new为修饰符,重写基类中的非虚相同方法 base.Display();/调用基类中的方法 Console.WriteLine(年级为:+sgrade);,class Worker:Person int wage;/增加私有字段工资 public int Wage get return wage;set wage=value;public void UpdateWage(int pwage)wage=pwage;public Worker(string pid,string pname,string psex,int pwage):base(pid,pname,psex)wage=pwage;new public void Display()base.Display();Console.WriteLine(工资为:+wage);,10.8 类的多态性,多态性指同一消息作用于不同的对象时,能够有不同的响应。C#支持两种类型的多态性:静态多态和动态多态。静态多态:通过方法重载来实现的,对象调用若干同名方法,系统在编译时,根据调用方法的参数个数、类型或顺序确定调用哪个同名方法,实现何种操作。动态多态:通过虚函数实现的,程序运行时,根据基类对象所具体引用的派生对象,调用派生对象的覆盖虚函数,实现不同的操作。静态多态与动态多态的区别:静态多态是在编译期间完成的,因此执行速度快;动态多态是在程序运行期间完成的,因此执行速度较慢,但它提高了程序的灵活性和抽象层次,使软件的可扩充性增强。,基类中虚函数定义格式为:public virtual 返回类型 虚函数名(参数列表)函数体 派生类中覆盖虚函数定义格式为:public override 返回类型 虚函数名(参数列表)函数体 必须注意:派生类中的覆盖虚函数必须和基类中虚函数的函数名,返回类型,参数个数及类型完全一致。static不能和virtual、override修饰符同时使用。因为虚函数的本质是实现动态多态性,通过运行时,基类对象所引用的派生对象,达到调用覆盖虚函数的目的。而static函数是通过类名进行调用,与对象无关。在一个类的继承体系中,可以采用sealed override的方法修饰一个虚函数,禁止后续派生类继续覆盖该虚函数。sealed(密封)不能修饰非虚函数。,【例10-7】用虚函数实现各类人员的显示。using System;namespace 教材_多态性 class Program static void Main(string args)Person personarray=new Person4;/定义Person基类数组/派生对象可以赋给基类对象 personarray0=new Student(320402199005011234,李刚,男,研一);personarray1=new Worker(320402199010101234,李魁,男,6700);personarray2=new Worker(320402196312091234,彭,女,2800);personarray3=new Person(320402197809091234,彭桃,女);foreach(Person tp in personarray)tp.Display();Console.ReadKey();,public class Person private string id;public string name;public string Sex get;set;public string Name get return name;set name=value;public string Id get return id;public virtual void Display()/virtual为修饰符,定义虚函数 Console.WriteLine(身份证号:0,姓名:1,性别为:2,Id,Name,Sex);public Person(string pid,string pname,string psex)name=pname;id=pid;Sex=psex;,public class Student:Person string sgrade;/增加私有字段 public string Sgrade get return sgrade;set sgrade=value;public void UpdateGrade(string pgrade)sgrade=pgrade;public Student(string pid,string pname,string psex,string pgrade):base(pid,pname,psex)sgrade=pgrade;public override void Display()/override为修饰符,覆盖基类中的相同虚函数 Console.WriteLine(身份证号:0,姓名:1,性别为:2,年级为:3,Id,Name,Sex,Sgrade);,public class Worker:Person int wage;public int Wage get return wage;set wage=value;public void UpdateWage(int pwage)wage=pwage;public Worker(string pid,string pname,string psex,int pwage):base(pid,pname,psex)wage=pwage;public override void Display()Console.WriteLine(身份证号:0,姓名:1,性别为:2,工资为:3,Id,Name,Sex,wage);,【例10-8】用虚函数和非泛型集合实现各类人员列表的数据处理。using System;using System.Collections;/非泛型集合命名空间using 教材_多态性;namespace 教材_多态性_非泛型集合 class Program static void Main(string args)ArrayList personlist=new ArrayList();personlist.Add(new Student(320402199409123456,蒋韵,女,大一);personlist.Add(new Worker(320402199312091234,李威,男,2700);personlist.Add(new Worker(320402197310011234,李香,女,15000);Console.WriteLine(personlist.Capacity);Console.WriteLine(personlist.Count);,foreach(Object tp in personlist)if(tp.GetType()=typeof(Student)(Student)tp).Sgrade=大二;else if(Worker)tp).Wage 3500)(Worker)tp).Wage=3500;foreach(Person tp in personlist)tp.Display();Console.ReadLine();,10.9 抽象类和抽象方法,1.抽象类抽象类表示一种抽象的概念,用作派生类的基类,以便所有其派生类具有相同的数据成员和函数成员。抽象类的定义格式为:abstract class 类名 访问权限修饰符 其他修饰符 类成员定义;2.抽象方法抽象方法的定义格式为:public abstract返回类型 函数名(参数列表)抽象方法描述的是成员函数的定义,而非实现,所以它没有函数体部分。,关于抽象类和抽象方法必须注意以下几点:,含有抽象方法的类,一定是抽象类。但抽象类并非一定要包含抽象方法,即抽象类中可以定义带实现的方法。抽象类只能作为派生类的基类,不能被实例化。不能同时使用Abstract和sealed修饰符声明抽象方法,因为抽象方法需要在派生类中被覆盖,而sealed修饰符禁止方法被覆盖。抽象方法逻辑上类似于虚方法,可以在派生类中被覆盖。从抽象类派生出的派生类,若全部覆盖了从抽象类中继承来的抽象方法,该类可以声明为非抽象类,能够实例化,否则,仍然为抽象类。,【例10-9】用抽象图形类实现矩形、圆等图形周长的求解。using System;namespace 教材_抽象类 class Program static void Main(string args)square f1=new Square(20,10);circle f2=new Circle(30,30,15);Console.WriteLine(f1.Perimeter();Console.WriteLine(f2.Perimeter();Console.ReadKey();public abstract class Figure public abstract float Perimeter();,public class Square:Figure private float length,width;public Square(float plength,float pwidth)length=plength;width=pwidth;public override float Perimeter()return 2*(length+width);public class Circle:Figure private float xc,yc,radium;public Circle(float pxc,float pyc,float pradium)xc=pxc;yc=pyc;radium=pradium;public override float Perimeter()return(float)(2*3.14*radium);,10.10委托,委托是C#语言提供的新的引用类型,即委托类型,也称代理类型。在功能上类似于C语言的函数指针,目的是通过委托类型对象去调用相同签名的函数。委托是从system.delegate中派生的,因此是类型安全的。采用委托可以实现通用程序的编写。委托的操作步骤:1.定义委托类型 2.定义委托对象 3.定义委托方法的实现 4.委托的调用,委托的定义步骤,1.定义委托类型委托类型定义格式为:修饰符 delegate 函数返回类型 委托类型名(函数形参列表)例如:public delegate bool dtest(Person p,string s);表示定义了一个委托类型,它能代表第一个参数为Person对象,第二个参数为string类型,返回值为bool类型的函数。,委托的定义步骤,2.定义委托对象委托对象定义格式为:委托类型名 委托对象名=new委托类型名(委托方法名);其中委托方法名可以是某个类的静态方法名,也可以是某个对象的实例方法名。方法的参数类型、返回类型必须与委托类型名的要求一致。例如:dtest d1=new dtest(Find.FindPeopleById);表示将委托对象d1代表Find类的FindPeopleById静态方法。具体见下面的例10-10。以后就可以通过调用d1来达到调用FindPeopleById函数的目的。,委托的定义步骤,3.定义委托方法的实现委托方法是真正要调用的函数,根据逻辑需要,在类中定义其实现过程。例如:public static bool FindPeopleById(Person p,string s)return p.Id=s;,委托的定义步骤,4.委托的调用格式为:委托对象名(实参列表);例如:d1(p1,320402196312091234);,委托类型案例,下面结合10-7案例中的类,学习委托类型的使用。【例10-10】用委托实现各类人员的身份查找和性别查找。using System;using 教材_多态性;/引用了多态性的类public delegate bool dtest(Person p,string s);/委托类型必须在类外定义,最好定义在命名空间外namespace 教材_委托 class Program static void Main(string args)Person personarray=new Person4;personarray0=new Student(320402199005011234,李刚,男,研一);personarray1=new Worker(320402199010101234,李魁,男,6700);personarray2=new Worker(320402196312091234,彭,女,2800);personarray3=new Person(320402197809091234,彭桃,女);dtest d1=new dtest(Find.FindPeopleById);/定义委托对象d1 int i=0;,foreach(Person tp in personarray)i+;/在遍历数组时,找到身份为320402196312091234的人就退出遍历 if(d1(tp,320402196312091234)break;personarrayi-1.Display();i=0;d1=new dtest(Find.FindPeopleBySex);/将委托对象代表FindPeopleBySex函数 foreach(Person tp in personarray)i+;if(d1(tp,男)break;/遍历数组时,找到性别为男的第一人就退出遍历 personarrayi-1.Display();Console.ReadKey();,class Find/定义查找类,提供人员的各种查找方法 public static bool FindPeopleById(Person p,string s)return p.Id=s;public static bool FindPeopleBySex(Person p,string s)return p.Sex=s;,【例10-11】用委托实现学生泛型集合中女同学的查找。using System;using System.Collections.Generic;using 教材_多态性;namespace 教材_委托_泛型 class Program static void Main(string args)List myList=new List();/定义学生泛型集合对象 myList.Add(new Student(320402199005011234,李刚,男,研一);myList.Add(new Student(320402199201014321,王君,女,博一);myList.Add(new Student(320402200009095678,向日葵,女,小六);List sl=myList.FindAll(FindStudentByMale);/调用FindAll,查找所有女生,foreach(Student s in sl)s.Display();Console.ReadLine();public static bool FindStudentByMale(Student p)/定义查找条件 return p.Sex=女;,10.11事件,事件就是当对象或类的状态发生改变时,对象或类发出的消息。事件是C#内置的语法,用户可以定义和处理事件。在C#中,事件是委托类型变量,可以代表事件的处理函数。事件的分类:事件分为窗体事件(窗体状态改变)、键盘事件(用户单击键盘)、鼠标事件(用户操作鼠标)、COMMAND事件(用户操作菜单)、用户自定义事件等。C事件用作对象之间的通信,发送状态改变消息的对象称为事件源,对事件进行处理的对象称为接收者。在事件源中定义事件,定义事件的触发方式;在事件接收者中定义事件响应函数的实现,并将该响应函数与事件源的事件进行关联;当有事件发生时,系统自动调用该事件的响应函数。,事件的定义步骤。,定义一个事件委托在事件源中定义一个事件在事件源中定义事件的触发方式在事件接收者中定义事件的响应函数在事件接收者中设置事件响应函数,也称为事件的预订,1.定义一个事件委托,public delegate void EventHandler(object sender,EventArgs e)EventHandler是事件委托名,用户可以自己定义名称。事件委托的定义格式是固定的,返回值为void型,第一个参数表示事件源对象,第二个参数是用于描述事件附加信息的对象,它必须是EventArgs类的派生类,若事件发生时不需要向事件处理程序传递附加信息,就直接使用EventArgs类的对象。事件不同,所附加的信息也不同,鼠标事件的附加信息为鼠标位置,键盘事件的附加信息为按键的ASCII码值。EventArgs类包含在System命名空间中。,2.在事件源中定义一个事件,如:public event EventHandler Changed;event是定义事件的关键字,事件名为Changed。,3.在事件源中定义事件的触发方式,如:在设置姓名时,触发事件Set.OnChanged(e);/设置事件触发方式,并响应事件public virtual void OnChanged(EventArgs e)if(Changed!=null)Changed(this,e);/this表示事件源对象本身,e表示事件附加信息对象,4.在事件接收者中定义事件的响应函数,如:static void PersonChanged(object sender,EventArgs e)/事件响应处理代码,5.在事件接收者中设置事件响应函数,也称为事件的预订,如:P1.Changed+=new EventHandler(PersonNameChanged);,【例10-12】用事件实现修改人员姓名时报告该人的老名字和新名字。using System;/定义事件的附加信息类,产生事件时,会将事件发送方(人员)的新旧名字传递给接收方public class MyEventArgs:System.EventArgs private string newname;private string oldname;public string NewName get return newname;set newname=value;public str