第八章 C#高级编程
目标 理解继承 在C# 中使用继承 在C#中使用接口 在C#中使用方法的重写 理解属性及其不同的类型、实现 理解和使用索引器 实现委托 定义和触发事件
继承 2-1 无需重新编写代码 基类 派生类 Class Base { // 成员变量 int basevar; // 成员函数 Base_fun1() // 定义 } ……. Class Derived : Base { // 成员变量 int derivedvars; // 成员函数 Derived_fun1() // 定义 } ……. 无需重新编写代码 void main() { Derived dr_obj = new Derived() ; dr_obj.Base_fun1(); }
继承 2-2 继承的层次结构示例 动物 // 成员函数 private Barking() 狗 马 基类 基类 派生类 派生类 Class Animal { // 成员变量 int eyes, nose; Animal() eyes = 2; nose = 1; } Pet_Animal() // 定义 继承的层次结构示例 基类 动物 基类 派生类 Class Dog : Animal { // 成员变量 // 成员函数 private Barking() // 定义 } private Wagging_Tail() 狗 马 派生类
X 继承 C# 中的类 多重继承 允许多重接口实现 public class Graduate: Student, Employee { // 成员变量 // 成员函数 } 允许多重接口实现
无法实现 GetInfo() 和 DispInfo() 方法 public class Student:Person { private string _school; private uint _eng; private uint _math; private uint _sci; public void GetMarks() Console.WriteLine(“请输入学校名称"); _school = Console.ReadLine(); Console.WriteLine("请分别输入英语、数学和自然科学的分数。"); _eng = uint.Parse(Console.ReadLine()); _math = uint.Parse(Console.ReadLine()); _sci = uint.Parse(Console.ReadLine()); Console.WriteLine(“所得总分为:{0}",_eng+_math+_sci); } public class Person { private string _name; private uint _age; public void GetInfo() Console.WriteLine("请输入您的姓名和年龄"); _name = Console.ReadLine(); _age = uint.Parse(Console.ReadLine()); } public void DispInfo() Console.WriteLine("尊敬的 {0},您的年龄为 {1} ", _name, _age); 基类 派生类 调用的基类成员 无法实现 GetInfo() 和 DispInfo() 方法 static void Main(string[] args) { Student objStudent = new Student(); objStudent.GetInfo(); objStudent.DispInfo(); objStudent.GetMarks(); } 演示
演示 派生类 基类 派生类 public class Student:Person { private string _schoolname; private uint _engscore; private uint _mathscore; private uint _sciscore; private uint _totscore; public uint GetMarks() Console.WriteLine(“请输入学校名称"); _schoolname = Console.ReadLine(); Console.WriteLine("请输入英语、数学和自然科学的分数。 "); _engscore = uint.Parse(Console.ReadLine()); _mathscore = uint.Parse(Console.ReadLine()); _sciscore = uint.Parse(Console.ReadLine()); _totscore = _engscore + _mathscore + _sciscore; Console.WriteLine("所得总分为:{0} ",_totscore); return _totscore; }} public class Person { private string _name; private uint _age; public void GetInfo() Console.WriteLine("请输入您的姓名和年龄"); _name = Console.ReadLine(); _age = uint.Parse(Console.ReadLine()); } public void DislayInfo() Console.WriteLine("尊敬的 {0},您的年龄为 {1}", _name, _age); public class UnderGraduate:Student { public void Check() Console.WriteLine("要上升一级,要求总分不低于 150 "); if(this.GetMarks() >=150) Console.WriteLine(“pass"); else Console.WriteLine(“not pass"); } 派生类 public static void Main(string[] args) { UnderGraduate objUnderGraduate = new UnderGraduate(); objUnderGraduate.GetInfo(); objUnderGraduate.DisplayInfo(); objUnderGraduate.Check(); } 演示
关键字 base 用于从派生类中访问基类成员 可以使用 base 关键字调用基类的构造函数
:base 关键字将调用 Person 类构造函数 public class Student:Person { private uint id; //调用 Person 构造函数 public Student(string name,uint age,uint id):base(name,age) this.id = id; Console.WriteLine(id); } :base 关键字将调用 Person 类构造函数
演示 static void Main(string[] args) { //构造 Student public class Person { public string _name; public uint _age; public Person(string name, uint age) this._name = name; this._age = age; Console.WriteLine(_name); Console.WriteLine(_age); } static void Main(string[] args) { //构造 Student Student objStudent = new Student("XYZ", 45, 001); } public class Student:Person { private uint _id; public Student(string name, uint age, uint id):base(name, age) this._id = id; Console.WriteLine(_id); } 还将调用 Base 构造函数 演示
关键字 override 基类 派生类 Class Base { // 成员变量 int basevar; // 成员函数 // 成员变量 int basevar; // 成员函数 GetInfo() // 定义 } ……. base 方法的新实现 Class Derived : Base { // 成员变量 int derivedvars; // 成员函数 override GetInfo() // 定义 } …….
关键字 virtual [访问修饰符] virtual [返回类型] 方法名 ( [参数列表] ) { ... } public virtual void Func() { Console.WriteLine(“这是 virtual 方法,可以被重写"); }
关键字 new 相同字段 隐藏继承成员 //要求 new 访问修饰符 new //将执行派生类的变量 将输出派生类中的 val
static void Main(string[] args) { class Student { public virtual void StuInfo() { Console.WriteLine(“此方法显示学生信息"); }} class ExpStudent: Student { public override void StuInfo() { base.StuInfo(); Console.WriteLine(“此方法重写 base 方法"); }} static void Main(string[] args) { ExpStudent objStudent = new ExpStudent(); objStudent.StuInfo(); Student objSuper = objStudent; objSuper.StuInfo(); }
抽象类和抽象方法 2-1 访问修饰符 abstract class ClassOne { //类实现 } 不能实例化 派生类的基类
抽象类和抽象方法 2-2 必须重写 抽象类 提供 派生类 重写方法 抽象方法 原型 abstract class Base { // 成员变量 int basevar; // 成员函数 abstract void base_fun1(parameters); // 无法实现 ……. } 抽象类 派生类 class Derived : Base { // 成员变量 int derivedvars; // 成员函数 override void Base_fun1(parameters) // 实际实现 ... } 重写方法 提供 抽象方法 原型 必须重写
演示 static void Main(string[] args) { Derv objB = new Derv(); using System; namespace Example_1 { abstract class ABC public abstract void AFunc(); public void BFunc() Console.WriteLine(“这是一个非抽象方法!"); } class Derv : ABC public override void AFunc() Console.WriteLine(“这是一个抽象方法! "); 抽象类 – 不能实例化 派生类 – 重写方法 static void Main(string[] args) { Derv objB = new Derv(); objB.AFunc(); objB.BFunc(); } 演示
static void Main(string[] args) { abstract class MyAbs { public abstract void AbMethod(); } //派生类 class MyClass : MyAbs public override void AbMethod() { Console.WriteLine(“在 MyClass 中实现的抽象方法"); //派生自 MyClass 的子类 class SubMyClass:MyClass public void General() //未实现 AbMethod 抽象方法 Console.WriteLine("在 SubMyClass 中未实现的抽象方法 "); static void Main(string[] args) { SubMyClass objSubClass = new SubMyClass(); objSubClass.General(); }
接口 3-1 请按开关按钮:ON/OFF OFF 两种方法 ON OFF ON
接口 3-2 接口 class IBase { void method1(); int method2(); //没有实现 ……. } interface 接口 只有方法声明 没有实现
示例中的 IPicture接口用于演示接口 接口 3-3 隐式声明为 public public interface IPicture { int DelImage(); void ShowImage(); } 无访问修饰符 示例中的 IPicture接口用于演示接口
演示 派生自 IPicture 接口 public class MyPicture : IPicture { //第一个方法的实现 public int DelImage() Console.WriteLine(“DelImage 实现!"); return(1); } //第二个方法的实现 public void ShowImage() Console.WriteLine(“ShowImage 实现!"); static void Main(string[] args) { MyPicture objM = new MyPicture(); objM.ShowImage(); int t = objM.DelImage(); Console.WriteLine(t); } 演示
演示 public interface IPicture { int DelImage(); void ShowImage(); } public class BaseIO { public void Open() Console.WriteLine("BaseIO 的 Open 方法"); } public interface IPicture { int DelImage(); void ShowImage(); } public class MyPicture : BaseIO, IPicture public int DelImage() Console.WriteLine(“DelImage 实现!"); return(1); public void ShowImage() Console.WriteLine(“ShowImage 实现!"); static void Main(string[] args) { MyPicture objM = new MyPicture(); objM.ShowImage(); int val = objM.DelImage(); Console.WriteLine(val); objM.Open(); } 演示
多重接口实现 C# 不允许多重类继承 但C#允许多重接口实现 这意味着一个类可以实现多个接口
演示 static void Main(string[] args) { MyPicture objM = new MyPiture(); objM.ShowImage(); objM.DelImage(); objM.ApplyAlpha(); objM.Open(); } public interface IPictManip { void ApplyAlpha(); } //第二个接口 public interface IPicture int DelImage(); void ShowImage(); public class BaseIO public void Open() Console.WriteLine(“BaseIO 的 Open 方法"); 演示
在 C# 中,只要不发生命名冲突,就完全可以允许多重接口实现 显式接口实现 在 C# 中,只要不发生命名冲突,就完全可以允许多重接口实现 public interface IPictManip { void ApplyAlpha(); } public interface IPicture { void ApplyAlpha(); } public class MyPicture : BaseIO, IPicture, IPictManip { public int ApplyAlpha() ....... } ? 使用显式接口实现
演示 static void Main(string[] args) { MyPicture objM = new MyPicture(); IPicture Pict = objM; //IPicture 引用 Pict.ShowImage(); IPictManip PictManip = objM; //IPictManip 引用 PictManip.ShowImage(); } public class MyPicture : BaseIO, IPicture, IPictManip { public int DelImage() Console.WriteLine(“DelImage 实现!"); return(1); } public void ApplyAlpha() Console.WriteLine(“ApplyAlpha 实现!"); void IPicture.ShowImage() Console.WriteLine(“ShowImage 的 IPicture 实现"); void IPictManip.ShowImage() Console.WriteLine(“ShowImage 的 IPictManip 实现"); 显式接口实现 演示
演示 public class MyPicture:IPictAll { public int DelImage() Console.WriteLine(“DelImage 实现!"); return(1); } public void ApplyAlpha() Console.WriteLine(“ApplyAlpha 实现!"); public void ApplyBeta() Console.WriteLine(“ApplyBeta 实现!"); public void ShowImage() Console.WriteLine(“DisplayImage 实现!"); public interface IPicture { int DelImage(); } public interface IPictManip void ApplyAlpha(); void ShowImage(); //继承多重接口 public interface IPictAll:IPicture, IPictManip void ApplyBeta(); static void Main(string[] args) { MyPicture objM = new MyPicture(); objM.ShowImage(); int val = objM.DelImage(); Console.WriteLine(val); objM.ApplyAlpha(); objM.ApplyBeta(); } 演示
自定义一个接口来实现IHomeworkCollector 自定义接口 如老师和学员都可以收作业, 那么收作业的方法应该放在哪个类? 调用收作业的方法不需要改变 A:Teacher类 B:Student类 C:两个都放 造成代码冗余 D:Person类 如果增加一个工程师类,他不会收作业 E:重新定义 如果继承这个类,Person类怎么办? 自定义一个接口来实现IHomeworkCollector
自定义接口 1、定义一个收作业的接口 public interface IHomeworkCollector { void CollectHomework(); } 2、在有收作业功能的类实现该接口 public class Student : Person, IHomeworkCollector 3、不同的类收作业方式不同 public void CollectHomework() { MessageBox.Show("报告老师!作业收集完毕!"); }
collector.CollectHomework(); 使用自定义接口 1、接口作为参数使用 private void DoCollectHomework(IHomeworkCollector collector) { collector.CollectHomework(); } 无论谁收作业这里都不需要做任何改变 DoCollectHomework(scofield); 2、接口作为返回值使用 private IHomeworkCollector CreateHomeworkCollector(string type){ switch (type) { case "student": collector = new Student("Scofield", Genders.Male, 28, "越狱狱"); break; … } return collector } 返回一个实现该接口的对象 collector.CollectHomework();
作为返回值和参数的意义 接口作为参数 传递了实现接口的对象 接口作为返回值 返回一个实现了接口的对象
接口和抽象类的对比 抽象类 接口 不 同 点 相同点 用 abstract 定义 用 interface 定义 只能继承一个类 可以实现多个接口 非抽象派生类必须实现抽象方法 实现接口的类必须实现所有成员 需要override实现抽象方法 直接实现 相同点 不能实例化 包含未实现的方法 派生类必须实现未实现的方法
委托 委托和方法必须具有相同的签名 在运行时确定调用哪种方法 Multiply(int,int) { …. } --- Divide(int,int) --- public delegate Call(int n1, int n2); 委托和方法必须具有相同的签名
[访问修饰符] delegate 返回类型 委托名(); 定义委托 2-1 class Delegates { public delegate int Call(int n1, int n2); class Math public int Multiply(int n1, int n2) // 实现 } public int Divide(int n1, int n2) {// 实现 语法 [访问修饰符] delegate 返回类型 委托名(); 将方法与委托关联起来 class TestDelegates { static void Main() { Call objCall; Math objMath = new Math(); objCall = new Call(objMath.Multiply); }
定义委托 2-2 将方法与委托关联起来 class Delegates { // 委托定义 public delegate int Call(int n1, int n2); class Math // 乘法方法 public int Multiply(int n1, int n2) return n1*n2; } // 除法方法 public int Divide(int n1, int n2) { if(n2!=0) return n1/n2; 定义委托 2-2 将方法与委托关联起来 static void Main(string[] args) { // 委托的对象 Call objCall; // Math 类的对象 Math objMath = new Math(); // 将方法与委托关联起来 objCall = new Call(objMath.Multiply); // 将委托实例化 result = objCall(4, 3); System.Console.WriteLine("结果为 {0}", result); }
不关心 事件 定义事件 事件的发布者 为对象订阅该事件 将发生的事件通知给订阅人 “请听题~” 其他人 抢答者 抢答者 事件源 宣布人 集中注意力聆听 未订阅该事件 其他人 抢答者 抢答者 事件的订阅人
定义事件 public delegate void delegateMe(); 语法 [访问修饰符] event 委托名 事件名; public delegate void delegateMe(); private event delegateMe eventMe;
订阅事件 eventMe += new delegateMe(objA.Method); eventMe += new delegateMe(objB.Method);
通知订阅对象 if(condition) { eventMe(); } 调用订阅特定事件的对象的所有委托
示例 class TestEvents { [STAThread] static void Main(string[] args) // 委托的对象 Delegate objDelegate = new Delegate(); // ClassA 的对象 ClassA objClassA = new ClassA(); // ClassB 的对象 ClassB objClassB = new ClassB(); // 订阅该事件 objDelegate.NotifyEveryOne += new Delegate.MeDelegate(objClassA.DispMethod); new Delegate.MeDelegate(objClassB.DispMethod); objDelegate.Notify(); } class ClassA { public void DispMethod() Console.WriteLine(“Class A 已接到 NotifyEveryOne 事件的通知!"); } // 第二个类 class ClassB Console.WriteLine(“Class B 已接到 NotifyEveryOne 事件的通知! "); class Delegate { // 定义委托 public delegate void MeDelegate(); // 定义事件 public event MeDelegate NotifyEveryOne; public void Notify() // 如果事件不为 null if(NotifyEveryOne != null) Console.WriteLine("触发事件:"); // 触发事件 NotifyEveryOne(); }
为什么只运行这个.exe文件就能使用我们程序的所有功能? 什么是程序集 为什么只运行这个.exe文件就能使用我们程序的所有功能?
向其他应用程序公开,并由这些应用程序使用 什么是程序集 .NET框架应用程序的生成块 包含编译好的代码的逻辑单元 创建的每个项目文件(project)都会产生一个程序集dll MyNewsReader.exe是“新闻快客”的程序集 程序集的结构 程序集清单 类型元数据 IL代码 资源 元数据是二进制信息 向其他应用程序公开,并由这些应用程序使用 程序中的图片、音乐文件
程序集清单 程序集清单:元数据重要部分 说明 信息 该信息用于从程序集导出的类型 有关被引用程序集的信息 控制对该程序集的类型和资源的引用如 何映射到包含其声明和实现的文件 类型引用信息 构成该程序集的文件 程序集中所有文件的列表 如果已经为程序集提供了一个强名称,则为来自发行者的公钥 强名称信息 有关该程序集支持的区域性或语言的信息 区域性 主版本号和次版本号,以及修订号和内部版本号 版本号 指定程序集名称的文本字符串 程序集名称 说明 信息
ILDasm ILDasm 反编译的工具。 可以查看IL汇编代码 可以看到类和方法 演示: 使用ILDasm
Visual Studio创建程序集 Visual Studio的所有应用程序都创建程序集 用于配置程序集的属性 AssemblyInfo.cs
AssemblyInfo.cs介绍 AssemblyInfo.cs文件 [assembly: AssemblyTitle("My News Reader")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jade Bird")] [assembly: AssemblyProduct("My News Reader")] [assembly: AssemblyCopyright("版权所有 (C) Jade Bird 2007")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
程序集与 internal 访问修饰符 internal修饰符用于程序集级别指定访问性 internal修饰的成员不能被其它程序集访问 访问权限 类内部 同一程序集的派生类 同一程序集的其他类 不同程序集的派生类 不同程序集的其他类 priavte 可以 不可以 protected internal public
什么是序列化 Profile对象 界面语言:英语 存储 介质 RssFeed对象 存储 代理服务器 … 序列化是将对象的状态存储到特定存储介质中的过程
特性 描述性关键字 对程序中的元素如:类、字段、方法、属性 命名时以Attribute结尾: SerializableAttribute abstract class FeedBase 标识这个类是可序列化的 可序列化就是这个类的一个特性 描述性关键字 对程序中的元素如:类、字段、方法、属性 命名时以Attribute结尾: SerializableAttribute 使用时省略Attribute 特性其实是一个类 class Program { [Obsolete("不要使用旧的方法, 请使用新的方法", true)] static void Old() { } static void New() { } public static void Main() Old(); } public sealed class SerializableAttribute 演示 MyAttribute
使用序列化 要存储的对象标记为可序列化,包括他的父类和属性的类 使用二进制方式存储对象 Serialize的用法: [Serializable] abstract class FeedBase 使用二进制方式存储对象 fileStream = new FileStream("profile.bin", FileMode.Create); BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(fileStream, Profile); 二进制格式化器 将对象以二进制方式格式化为流 Serialize的用法: Serialize ( Stream serializationStream, Object graph) 流 对象
序列化的过程 格式化程序 对象可否序列化 N 序列化对象中的子类成员 Y 子类成员 可否序列化 N 异常退出 Y 将对象格式化 写入存储介质
反序列化 把Profile对象存储成文件,怎么取出来呢? Profile对象 界面语言:英语 存储 介质 RssFeed对象 读取 代理服务器 … 数据转换为对象 反序列化则是从特定存储介质中的数据重新构建对象的过程
使用反序列化 将序列化好的Profile数据反序列化为对象 Deserialize的用法: fileStream = new FileStream("profile.bin", FileMode.Open); BinaryFormatter bf = new BinaryFormatter(); Profile = (Profile)bf.Deserialize(fileStream); 将指定流反序列化 类型转换 Deserialize的用法: 需要类型转换 public Object Deserialize ( Stream serializationStream ) 对象 流
序列化与反序列化 几个重要的类 SerializableAttribute Formatter 可以将对象从一个应用程序发送到另一个应用程序中 使用场合 Web Service 分布式应用
这种机制叫做反射(Reflection) 可以ILDasm反编译工具浏览一个dll和exe的构成 这种机制叫做反射(Reflection) 应用程序信息 类的属性 应用程序或dll 类的方法 反射 … 用于在运行时通过编程方式获得类型信息
现场演示 通过代码演示获取dll的版本号 using System.Reflection; 引入命名空间 class Program { static void Main(string[] args) string version = Assembly.LoadFile(@"D:\MyNewsReader.exe") .GetName().Version.ToString(); Console.WriteLine(version); } 反射
总结2-1 继承是获得现有类的功能的过程 创建新类所根据的基础类称为基类或父类,新建的类则称为派生类或子类 base 关键字用于从派生类中访问基类成员 override 关键字用于修改方法、属性或索引器。new 访问修饰符用于显式隐藏继承自基类的成员 抽象类是指至少包含一个抽象成员(尚未实现的方法)的类。抽象类不能实例化 重写方法就是修改基类中方法的实现。virtual 关键字用于修改方法的声明
总结2-2 显式接口实现是用于在名称不明确的情况下确定成员函数实现的是哪一个接口 委托包含对方法而不是方法名称的引用 C# 中的事件允许一个对象将发生的事件或修改通知其他对象