手机网站开发视频,宣传方式有哪些,百度推广seo效果怎么样,wordpress下载页插件下载C# 学习笔记 Chapter 2 比较硬的基础部分Section 1 委托Part 1 Action 与 func 委托的示例Part 2 自定义委托Part 3 委托的一般使用Part 4 委托的高级使用Part 5 适时地使用接口 Interface 取代一些对委托的使用 Section 2 事件Part 1 初步了解事件Part 2 事件的应用Part 3 事件… C# 学习笔记 Chapter 2 比较硬的基础部分Section 1 委托Part 1 Action 与 func 委托的示例Part 2 自定义委托Part 3 委托的一般使用Part 4 委托的高级使用Part 5 适时地使用接口 Interface 取代一些对委托的使用 Section 2 事件Part 1 初步了解事件Part 2 事件的应用Part 3 事件的声明Part 4 澄清 Section 3 类Part 1 类的声明Part 2 类的继承Part 3 成员的继承与访问Part 4 面向对象的实现风格Part 5 重写与多态 Section 4 类的扩展知识Part 1 抽象类Part 2 接口Part 3 单元测试与依赖倒置原则Part 4 接口隔离原则Part 5 反射 待补充Part 6 泛型 Generic 待补充 Chapter 2 比较硬的基础部分 Section 1 委托
什么是委托
委托 Delegate 是函数指针的升级版Delegate 的意思是这有一件事情我不亲自去做而是交给别人去做也就是间接地去做
#include studio.hint Add(int a, int b)
{int result a b;return result
}int Sub(int a, int b)
{int result a - b;return result
}int main()
{int x 100;int y 200;int z 0;z Add(x, y);printf(%d%d%d, x, y, z);z Sub(x, y);printf(%d-%d%d, x, y, z);system(pause);return 0;
}我们可以看到输出结果如下 100200300100-200-100Press any key to continue ...在这个例子里是通过函数的名字来调用是直接调用
#include studio.htypedef int (* Calc)(int a, int b); // 函数指针并且定义为一种类型int Add(int a, int b)
{int result a b;return result
}int Sub(int a, int b)
{int result a - b;return result
}int main()
{int x 100;int y 200;int z 0;Calc funcPoint1 Add;Calc funcPoint2 Sub; z funcPoint1(x, y);printf(%d%d%d, x, y, z);z funcPoint2(x, y);printf(%d-%d%d, x, y, z);system(pause);return 0;
}我们可以看到输出结果如下 100200300100-200-100Press any key to continue ...可以看到输出结果是一样的这就说明了间接调用和直接调用的效果是一样的这就是C语言中的函数指针
一切皆地址 变量数据是以某个地址为起点的一段内存中所存储的值 函数算法是以某个地址为起点的一段内存中所存储的一组机器语言指令 直接调用与间接调用 直接调用通过函数名来调用函数处理器通过函数名直接获得函数所在的地址并开始执行 - 返回 间接调用通过函数指针来调用函数处理器通过读取函数指针存储的值获得函数所在地址并开始执行 - 返回 Java 中没有委托相对应的功能实体委托的简单实用 Action 委托Void类型用 Func 委托有参数的用
Part 1 Action 与 func 委托的示例
namespace ConsoleHelloWorld
{class Program{static void Main(string[] args){Calculator calculator new Calculator();Action action new Action(calculator.Report); // 注意这里没有圆括号这里只需要方法名而不是调用方法calculator.Report(); // 直接调用action.Invoke(); // 间接调用模仿函数指针的写法action(); // 间接调用简洁的写法// 参数参数返回类型Funcint, int, int func new Funcint, int, int(calculator.Add); Funcint, int, int func2 new Funcint, int, int (calculator.Sub);int x 100;int y 200;int z 0;// 间接调用函数指针式的写法z func.Invoke(x, y);Console.WriteLine(z);z func2.Invoke(x, y);Console.WriteLine(z);// 间接调用简洁的写法z func(x, y);Console.WriteLine(z);z func2(x, y);Console.WriteLine(z);}}class Calculator{public void Report(){Console.WriteLine(I have 3 methods);}public int Add(int a, int b){ int result a b;return result;}public int Sub(int a, int b){int result a - b;return result;}}
}运行上面的程序可以获得如下的输出
I have 3 methods
I have 3 methods
I have 3 methods
300
-100
300
-100Part 2 自定义委托
由于委托是一种类 class类是一种数据类型且是引用类型的数据类型委托可以声明变量和声明实例委托的声明方式与一般的类的声明方式并不相同更像是 C/C 中函数指针的声明方式
下面这个例子是自定义委托的声明与使用
namespace ConsoleHelloWorld
{public delegate double Calc(double x, double y);// delegate 是类需要声明在名称空间体里面// public 是访问范围delegate 是告诉编译器要声明一个委托// 第一个 double 是目标方法的返回值类型// 然后 Calc 是委托的名字// 后面的圆括号里面是目标方法的参数列表// 到此自定义委托类型声明完成class Program{static void Main(string[] args){Calculator calculator new Calculator();// 传递的方法的参数列表必须和声明时一样返回类型也必须一致Calc calc1 new Calc(calculator.Add); Calc calc2 new Calc(calculator.Sub);Calc calc3 new Calc(calculator.Mul);Calc calc4 new Calc(calculator.Div);double a 100;double b 200;double c 0;c calc1.Invoke(a, b);Console.WriteLine(c);c calc2.Invoke(a, b);Console.WriteLine(c);c calc3.Invoke(a, b);Console.WriteLine(c);c calc4.Invoke(a, b);Console.WriteLine(c);}}class Calculator{// 有四个方法除了名字不同返回值类型和参数列表都是一样的public double Add(double x, double y){ return x y;}public double Sub(double x, double y){return x - y;}public double Mul(double x, double y){return x * y;}public double Div(double x, double y){return x / y;}}
}运行上面的代码可以获得以下的输出
当我们自定义委托的时候需要注意几点
委托与所封装的方法必须保持“类型兼容”声明委托的时候不要放错位置委托是类需要声明在名称空间体里面放错了可能导致运行不了或成为嵌套类 上图可以看到第一行是委托的声明下面四行是与之兼容的方法
Part 3 委托的一般使用
在工作中一般是把委托当做参数传到另一个方法里去这样做的好处可以间接调用委托所封装的方法形成一个动态调用方法的结构
模版方法写了一个方法通过传进来的委托参数借用指定的外部方法来产生结果 相当于 填空题 常位于代码中部 委托有返回值 相当于写了一个方法是模版这个模版里有一处是不确定的其他地方是确定好的这个不确定的部分就靠传进来的委托类型的参数所包含的方法来填补 回调方法 callback调用制定的外部方法 相当于流水线 常位于代码末尾 委托没有返回值通常用来处理一些末尾的工作
下面展示的使模板方法的使用
namespace ConsoleHelloWorld
{class Program{static void Main(string[] args){ProductFactory productFactory new ProductFactory();WrapFactory wrapFactory new WrapFactory();FuncProduct func1 new FuncProduct(productFactory.MakePizza);FuncProduct func2 new FuncProduct(productFactory.MakeToyCar);Box box1 wrapFactory.WrapProduct(func1);Box box2 wrapFactory.WrapProduct(func2);Console.WriteLine(box1.Product.Name);Console.WriteLine(box2.Product.Name);}}class Product{ public string Name { get; set; } }class Box{ public Product Product { get; set; } }class WrapFactory{public Box WrapProduct ( FuncProduct getProduct ){// 模板方法Box box new Box();// 执行传进来的委托所封装的方法这就是间接调用Product product getProduct.Invoke(); // 获取产品将产品装入 Boxbox.Product product; return box;// 写成模版方法的好处是Product类Box类还有WrapFactory类都不需要在修改// 只需要扩展产品工厂让其产出更多的产品不管生产哪种产品的方法// 只需要将该方法封装在委托类型的对象里传给模版方法这个模版方法一定可以将// 产品包装成箱子返回回来极大地实现代码的重复使用}}class ProductFactory{public Product MakePizza(){ Product product new Product();product.Name Pizza;return product;}public Product MakeToyCar(){ Product product new Product();product.Name Toy Cat;return product;}}
}下面展示的是回调方法的使用
namespace ConsoleHelloWorld
{class Program{static void Main(string[] args){ProductFactory productFactory new ProductFactory();WrapFactory wrapFactory new WrapFactory();FuncProduct func1 new FuncProduct(productFactory.MakePizza);FuncProduct func2 new FuncProduct(productFactory.MakeToyCar);Logger logger new Logger();ActionProduct log new ActionProduct(logger.Log);Box box1 wrapFactory.WrapProduct(func1, log);Box box2 wrapFactory.WrapProduct(func2, log);Console.WriteLine(box1.Product.Name);Console.WriteLine(box2.Product.Name);}}class Logger{public void Log(Product product){// Log 以回调的形式传进模版的方法里Console.WriteLine(Product {0} created at {1}. Price is {2}., product.Name, DateTime.UtcNow, product.Price);}}class Product{ public string Name { get; set; }public double Price { get; set; }}class Box{ public Product Product { get; set; } }class WrapFactory{public Box WrapProduct ( FuncProduct getProduct, ActionProduct logCallback){// 模板方法Box box new Box();// 执行传进来的委托所封装的方法这就是间接调用Product product getProduct.Invoke(); // 获取产品将产品装入 Boxif (product.Price 50){ logCallback(product);}box.Product product; return box;// 写成模版方法的好处是Product类Box类还有WrapFactory类都不需要在修改// 只需要扩展产品工厂让其产出更多的产品不管生产哪种产品的方法// 只需要将该方法封装在委托类型的对象里传给模版方法这个模版方法一定可以将// 产品包装成箱子返回回来极大地实现代码的重复使用}}class ProductFactory{public Product MakePizza(){ Product product new Product();product.Name Pizza;product.Price 20;return product;}public Product MakeToyCar(){ Product product new Product();product.Name Toy Cat;product.Price 120;return product;}}
}无论是模版方法还是回调方法都使用委托类型的参数封装一个外部的方法然后把这个方法传进方法的内部进行间接调用 这个就是委托的常规用法。 委托如果被滥用的后果非常危险
这时一种方法级别的紧耦合现实工作中要慎之又慎使可读性下降、debug难度增加把委托回调、异步调用和多线程纠缠在一起会让代码难以维护和阅读是灾难级的委托的使用不当有可能造成内存泄漏和程序性能下降
Part 4 委托的高级使用 多播委托指的是一个委托内部封装了不止一个方法下面是例子
namespace ConsoleHelloWorld
{class Program{static void Main(string[] args){Student student1 new Student() { Id 1, PenColor ConsoleColor.Yellow };Student student2 new Student() { Id 2, PenColor ConsoleColor.Green };Student student3 new Student() { Id 3, PenColor ConsoleColor.Red };Action action1 new Action(student1.DoHomework);Action action2 new Action(student2.DoHomework);Action action3 new Action(student3.DoHomework);// 多播委托的写法action1 action2; // 将 aciton2 合并到 action1action1 action3;action1.Invoke();// 多播委托的执行顺序是按照你封装方法的顺序执行的}}class Student{ public int Id { get; set; }public ConsoleColor PenColor { get; set; }public void DoHomework(){ for (int i 0; i 5; i) {Console.ForegroundColor this.PenColor;Console.WriteLine(Student {0} doing homework {1} hours., this.Id, i);Thread.Sleep(1000); // 线程暂停一秒钟}}}
}隐式异步调用
异步调用与同步调用是相对的 同步你做完了我在你的基础上接着做 异步咱们两个同时做也就是各做各的 同步调用与异步调用的对比 每一个运行的程序是一个进程 process 每一个进程可以有一个或者多个线程 thread第一个线程叫做主线程之外的是分支线程 同一个线程内调用方法的时候是前一个执行完后一个才能执行叫做同步调用 异步调用的底层机理是多线程 同步调用是单线程串行调用异步调用是多线程并行调用
下面是同步调用的异步调用的例子
namespace ConsoleHelloWorld
{class Program{static void Main(string[] args){Student student1 new Student() { Id 1, PenColor ConsoleColor.Yellow };Student student2 new Student() { Id 2, PenColor ConsoleColor.Green };Student student3 new Student() { Id 3, PenColor ConsoleColor.Red };// 直接同步调用student1.DoHomework();student2.DoHomework();student3.DoHomework();Console.WriteLine();Action action1 new Action(student1.DoHomework);Action action2 new Action(student2.DoHomework);Action action3 new Action(student3.DoHomework);// 使用委托的隐式异步调用action1.BeginInvoke(null, null);action2.BeginInvoke(null, null);action3.BeginInvoke(null, null);Console.WriteLine();// 使用委托的显式异步调用Task task1 new Task(new Action(student1.DoHomework));Task task2 new Task(new Action(student2.DoHomework));Task task3 new Task(new Action(student3.DoHomework));task1.Start();task2.Start();task3.Start();Console.WriteLine();// 单播委托的间接同步调用action1.Invoke();action2.Invoke();action3.Invoke();Console.WriteLine();// 多播委托的间接同步调用action1 action2;action2 action3;action1();Console.WriteLine();}}class Student{ public int Id { get; set; }public ConsoleColor PenColor { get; set; }public void DoHomework(){ for (int i 0; i 5; i) {Console.ForegroundColor this.PenColor;Console.WriteLine(Student {0} doing homework {1} hours., this.Id, i);Thread.Sleep(1000); // 线程暂停一秒钟}}}
}Part 5 适时地使用接口 Interface 取代一些对委托的使用
委托使用不当回提高代码的维护难度使用接口可以避免这些不必要的麻烦还可以获得相同的功能
namespace ConsoleHelloWorld
{class Program{static void Main(string[] args){IProductFactory pizzaFactory new PizzaFactory();IProductFactory toycarFactory new ToyFactory();WrapFactory wrapFactory new WrapFactory();Logger logger new Logger();ActionProduct log new ActionProduct(logger.Log);Box box1 wrapFactory.WrapProduct(pizzaFactory, log);Box box2 wrapFactory.WrapProduct(toycarFactory, log);Console.WriteLine(box1.Product.Name);Console.WriteLine(box2.Product.Name);}}interface IProductFactory{Product Make();}class PizzaFactory : IProductFactory // 这个类实现了IProductFactory的接口{public Product Make(){// 重构是指基本不改变原来的代码,只是把代码放到更合适的地方去Product product new Product();product.Name Pizza;product.Price 20;return product;}}class ToyFactory : IProductFactory{public Product Make(){Product product new Product();product.Name Toy Cat;product.Price 120;return product;}}class Logger{public void Log(Product product){// Log 以回调的形式传进模版的方法里Console.WriteLine(Product {0} created at {1}. Price is {2}., product.Name, DateTime.UtcNow, product.Price);}}class Product{public string Name { get; set; }public double Price { get; set; }}class Box{public Product Product { get; set; }}class WrapFactory{public Box WrapProduct(IProductFactory productFactory, ActionProduct logCallback){// 模板方法Box box new Box();Product product productFactory.Make(); if (product.Price 50){logCallback(product);}box.Product product;return box;}}
}可以看到重构之后使用接口之后程序没有委托的身影也就没有方法级别的耦合 这个例子说明可以使用接口取代委托 Section 2 事件
Part 1 初步了解事件
定义Event译为“事件” 能够发生的东西特别是一些比较重要的 a thing that happens, especially something important. 通顺的解释就是“能够发生的什么事情”叫做事件 角色使对象或类具备通知能力的成员 在 C# 语言中事件是一种类型的成员是一种使对象或类能够提供通知的成员 An event is a member that enables an object or class to provide notifications. 对象 A 拥有一个时间 B的意思是当B发生的时候A有能力通知别的对象 经由事件发送出来的与事件本身相关的消息称为 事件参数 EventArgs 根据同时和事件参数来采取行动的行为称为响应时间或处理事件处理事件时所做的事情就叫做事件处理器 Event Handler. 使用用于对象或类之间的动作协调与信息传递消息推送 事件的功能 通知别的对象或者类 可选的事件参数即详细信息 原理事件模型event model也叫做发生-响应模型中的两个 “5” “发生 - 响应”中的五个部分闹钟响了你起床、孩子饿了你做饭…这里面隐含着“订阅”的关系 “发生 - 响应”中的五个动作 1我有一个事件 2一个人或一群人关心我的这个事件 3我的这个事件发生了 4关心这个事件的人会被一次通知到 5被通知到的人根据拿到的事件信息又称“时间数据”、“事件参数”、“通知”对事件进行相应又称“处理事件” 需要规定一下相关的术语以便于交流和学习 事件的订阅者与事件消息的接收者、时间的响应者、事件的处理者、被事件所通知的对象是一样的便于交流只用事件的订阅者 事件参数与事件信息、事件消息、事件数据是一样的便于交流只使用事件参数 提示 事件多用于桌面、手机等开发的客户端编程因为这些客户端程序经常是用户通过事件来“驱动”的 事件模型是从现实世界抽象而来的各种编程语言对这个机制的实现方法不尽相同 Java 语言里没有事件这种成员也没有委托这种数据类型Java的事件是使用接口来实现的 事件模式是好东西但是是有缺陷的如果编写的时候没有约束程序的逻辑容易混乱经过长期的总结下来总结出MVCMVPMVVM等架构模式这些是事件模式更高级、更有效的用法 日常开发的时候使用已有事件的机会比较多自己声明事件的机会比较少
Part 2 事件的应用
事件模型的五个组成部分 事件的拥有者 event source对象事件是被拥有者的内部触发的 事件成员也就是事件本身event成员 事件的响应者也就是事件的订阅者 event subscriber对象当事件发生的时候有哪些对象被通知到了就是事件响应者 事件的处理器 event handler成员本质上是一个回调方法 事件的订阅把事件处理器与事件关联在一起本质上是一种以委托类型为基础的“约定” 注意 事件处理器是方法成员 挂接事件处理器的时候可以使用委托实例也可以直接使用方法名这是个语法糖 事件处理器对事件的订阅不是随意地匹配是否声明事件时所使用的委托类型来检测 事件可以同步调用也可以异步调用
下面是一个小例子
namespace ConsoleHelloWorld
{class Program{static void Main(string[] args){System.Timers.Timer timer new System.Timers.Timer(); // 事件拥有者 timertimer.Interval 1000; // msBoy boy new Boy(); // 事件的响应者是 boy 对象Girl girl new Girl();timer.Elapsed boy.Action;// 是订阅的写法后面要跟上事件响应者的事件处理器timer.Elapsed girl.Action;// 事件 Elapsed事件订阅 timer.Start();Console.ReadLine();}}class Boy{// 事件的处理器internal void Action(object sender, ElapsedEventArgs e){Console.WriteLine(Jump!);}}class Girl{internal void Action(object sender, ElapsedEventArgs e){Console.WriteLine(Sing!);}}
}上面展示的是一个事件同时有两个事件处理器的时候的样子 上图展示的是标准的事件机制模型结构清晰是MVC、MVP等设计模式的雏形 下面的程序是对这个标准的事件机制模型的解释
namespace WindowsFormsApp1
{internal static class Program{/// summary/// The main entry point for the application./// /summary[STAThread]static void Main(){Form form new Form(); // 事件的拥有者 formController controller new Controller(form); // 事件的响应者 controllerform.ShowDialog();}}class Controller{private Form form;public Controller(Form form){if (form ! null){this.form form;this.form.Click this.FormClicked; // 事件是 form 的 click实现事件订阅}}// 事件处理器private void FormClicked(object sender, EventArgs e){this.form.Text DateTime.Now.ToString();}}
}上图展示的是对象用自己的方法订阅、处理自己的事件 下面的程序是对上图的解释同时接触到了什么是派生
namespace WindowsFormsApp1
{internal static class Program{/// summary/// The main entry point for the application./// /summary[STAThread]static void Main(){MyForm form new MyForm(); // 事件的拥有者 form事件的响应者也是 fromform.Click form.FormClicked; // 事件是 Click事件的订阅是 form.ShowDialog();}}class MyForm : Form // 派生集成原有的方法之外还可以添加新的方法{// 事件处理器internal void FormClicked(object sender, EventArgs e){this.Text DateTime.Now.ToString();}}
}上图展示的是使用最多的特点是事件的拥有者是事件的响应者的字段成员是Windows上默认的事件订阅和处理结构 下面的程序是对上图示例的解释
namespace WindowsFormsApp1
{internal static class Program{/// summary/// The main entry point for the application./// /summary[STAThread]static void Main(){MyForm form new MyForm();form.ShowDialog();}}// 事件的响应者是 MyForm 的对象class MyForm : Form{private TextBox textBox;private Button button; // button 是事件的拥有者且为字段成员public MyForm(){this.textBox new TextBox();this.button new Button();// 显示在 form 当中this.Controls.Add(this.textBox);this.Controls.Add(this.button);this.button.Click this.ButtonClicked; // 事件是 Click// 是事件的订阅this.button.Text Say Hello!;this.button.Top 100;}// 事件的处理器private void ButtonClicked(object sender, EventArgs e){this.textBox.Text Hello World;}}
}Part 3 事件的声明
完整的事件声明方式 示例
namespace ConsoleHelloWorld
{class Program{static void Main(string[] args){Customer customer new Customer(); // 事件的拥有者Waiter waiter new Waiter(); // 事件的响应者customer.Order waiter.Action; // 使用 Action 的方法作为 waiter 类型的事件处理器// Order 事件 事件的订阅customer.Action();customer.PayTheBill();}}public class OrderEventArgs : EventArgs{public string DishName { get; set; }public string Size { get; set; }}public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);public class Customer // 需要保证访问级别是一致的{ private OrderEventHandler orderEventHandler;// 事件 Orderpublic event OrderEventHandler Order {add {this.orderEventHandler value;}remove{this.orderEventHandler - value;}}public double Bill { get; set; }public void PayTheBill(){Console.WriteLine(I will pay ${0}, this.Bill);}public void WalkIn(){Console.WriteLine(Walk into the restaurant.);}public void SitDown(){Console.WriteLine(Sit Dowm.);}public void Think(){for (int i 0; i 5; i){Console.WriteLine(Let me think.......);Thread.Sleep(1000);}if (this.orderEventHandler ! null){ OrderEventArgs e new OrderEventArgs();e.DishName Kongpao Chicken;e.Size large;this.orderEventHandler.Invoke(this, e);}}public void Action(){Console.ReadLine();this.WalkIn(); ;this.SitDown();this.Think();}}// 事件的响应者public class Waiter{internal void Action(Customer customer, OrderEventArgs e){Console.WriteLine(I will serve you the dish - {0}, e.DishName);double price 10;switch (e.Size){case small:price price * 0.5;break;case large:price price * 1.5;break;default:break;}customer.Bill price;}}
}简略的事件声明方式 示例
namespace ConsoleHelloWorld
{class Program{static void Main(string[] args){Customer customer new Customer(); // 事件的拥有者Waiter waiter new Waiter(); // 事件的响应者customer.Order waiter.Action; // 使用 Action 的方法作为 waiter 类型的事件处理器// Order 事件 事件的订阅customer.Action();customer.PayTheBill();}}public class OrderEventArgs : EventArgs{public string DishName { get; set; }public string Size { get; set; }}public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);public class Customer // 需要保证访问级别是一致的{public event OrderEventHandler Order;public double Bill { get; set; }public void PayTheBill(){Console.WriteLine(I will pay ${0}, this.Bill);}public void WalkIn(){Console.WriteLine(Walk into the restaurant.);}public void SitDown(){Console.WriteLine(Sit Dowm.);}public void Think(){for (int i 0; i 5; i){Console.WriteLine(Let me think.......);Thread.Sleep(1000);}if (this.Order ! null){ OrderEventArgs e new OrderEventArgs();e.DishName Kongpao Chicken;e.Size large;this.Order.Invoke(this, e);}}public void Action(){Console.ReadLine();this.WalkIn(); ;this.SitDown();this.Think();}}// 事件的响应者public class Waiter{internal void Action(Customer customer, OrderEventArgs e){Console.WriteLine(I will serve you the dish - {0}, e.DishName);double price 10;switch (e.Size){case small:price price * 0.5;break;case large:price price * 1.5;break;default:break;}customer.Bill price;}}
}为什么有了委托类型的字段还需要事件
事件成员可以让程序的逻辑和对象之间的关系变得更加有道理、安全
Part 4 澄清 Section 3 类
面向对象编程有三个主要特征封装、继承和多态 什么是类
是一种数据结构 data structure是一种数据类型
namespace ConsoleHelloWorld {class Program {static void Main(string[] args) {Student student new Student(id: 1, name: Tim); // 创建实例student.Report();}}class Student {public Student(int id, string name) {this.ID id; ;this.Name name;}// 析构器手动释放资源// 当程序关闭之前变量 student 没人引用系统会自动调用析构器~ Student() {Console.WriteLine(Bye bye, Release the system resources);}public int ID { get; set; }public String Name { get; set; }public void Report() {Console.WriteLine($Im #{this.ID} student, my name is {this.Name}.);}}
}Part 1 类的声明
类可以声明在以下三个位置 名称空间之内最常见的 显式名称空间之外实际上是声明在全局名称空间里面 类体里面成员类或者叫做嵌套类用得也比较多
声明 Declaration定义是 Definition在 C#和Java中可以混用理解但在 C 当中这二者不是一个东西。在 C 中类的声明与类的定义是分开的C 如果要应用类是需要引用头文件而在 C# 和 Java 当中是不需要的 因此在 C# 语言中声明即定义 类命名中的 class 类名 和 类体 是不可缺少的 类修饰符是可选的 new public protected internal private abstract sealed static new 修饰符适用于嵌套类它指定类隐藏同名的继承成员如果在不是嵌套类声明的类声明中使用 new 修饰符则会导致编译时的错误 public、protected、internal 和 private 修饰符将控制类的可访问性根据类声明所处的上下文这其中的一些修饰符可能不允许使用 abstract、sealed 和 static 是有关继承的 public class 其他的项目是可以访问的也就是其他的 Assembly 可以访问这个 Assembly 中的类 internal class 是可以在同一个项目中自由访问的也就是把访问级别限制在项目也就是 Assembly 当中 private class 仅当这个class是其他类的成员的时候才能见到
Part 2 类的继承
对于面向对象编程非常重要其最显著的特征是封装、继承和多态而多态是基于继承的
声明继承类的语法是比较简单的
class 类名 : 类基础 类体一般来说基类和派生类是一对父类和子类是一对
namespace ConsoleHelloWorld {class Program {static void Main(string[] args) {Type type typeof(Car);Type baseType type.BaseType;Console.WriteLine(baseType.FullName);// 可以看到输出 ConsoleHelloWorld.Vehicle// 说明 Car 的基类是 Vehicle// 是一个 is a 概念// 一个派生类的实例也是基类的实例但反过来不正确// 也就是 Car 的实例也是 Vehicle 的实例// 也就是一个汽车也是一个交通工具Car car new Car(); // car 是实例引用着 new 操作符创建的实例Console.WriteLine(car is Vehicle);// 可以看到输出 TrueVehicle vehicle new Vehicle();Console.WriteLine(vehicle is Car);// 可以看到输出 False// 从 是一个 is a 概念引出 可以用一个父类类型的变量引用子类类型的实例// 在多态中是非常有用的下面是引出概念的示例Vehicle vehicle1 new Car();Object o1 new Vehicle();Object o2 new Car();}}// 作为基类// 如果声明一个类没有说基类是谁// 下面的例子编译器等效 class Vehicle : Object { }class Vehicle { }// 由 Vehicle 派生而来Car 继承了 Vehicleclass Car : Vehicle { }
}需要注意 如果使用 sealed 来修饰类那么这个类就不能作为基类来使用 C# 中一个类最多只允许有一个基类但可以有多个基接口
规范一下用语 继承/派生自某个基类 某个类实现了某个基接口
子类的访问级别不能超过父类的访问级别
Part 3 成员的继承与访问
继承的本质是派生类在基类已有的成员上进行横向和纵向的扩展
派生类基于基类目前已有的成员当继承发生的时候子类对父类的成员是全盘获得的父类里有什么成员子类就全部获得派生与继承的过程当中进行的是扩展类成员只可能是越来越多不可能是越来越少上述两点导致了一个类成员一旦被引入到继承链当中就会一直向下传递不可能被从继承链中移除掉这个是静态语言的特点类成员在数量上的扩充就是横向扩展不增加类成员的个数而是对某个类成员的版本进行扩充就是纵向扩展也就是重写 override
例子1继承链
namespace ConsoleHelloWorld {class Program {static void Main(string[] args) {RaceCar raceCar new RaceCar();string owner raceCar.Owner; // Owner 声明在 Vehicle 当中在继承链中继承下来了这个 Owner 是去不掉的}}// 基类class Vehicle {public string Owner { get; set; }}class Car : Vehicle { }class RaceCar : Car { }
}例子2什么是基类对象以及如何通过基类对象对基类的类成员进行访问
namespace ConsoleHelloWorld {class Program {static void Main(string[] args) {Car car new Car();// 继承链上的类当我们创建实例的时候// 先从基类的构造器开始先构造基类对象再一级一级构造// 最终构造创建的子类对象car.ShowOwner();}}// 基类class Vehicle {// 实例构造器public Vehicle(string owner){this.Owner owner;}public string Owner { get; set; }}class Car : Vehicle {public Car() : base(N/A) // 调用 Vehicle 构造器的时候传入一个值或者和 Vehicle 构造器的参数列表一致{this.Owner Car Owner;}public void ShowOwner() {Console.WriteLine($this class owner value: {this.Owner}); // this. 子类对象上的值Console.WriteLine($base class owner value: {base.Owner}); // base. 基类对象上的值base. 只能向上访问一层// 在这个例子中二者的值是一样的因为 Car 继承下来了父类的全部成员// 所以 this.Owner 和 base.Owner 指向的使内存中同一个值}}class RaceCar : Car { }
}
类成员的访问级别是以类的访问级别为上限
Part 4 面向对象的实现风格
Class-based 基于类的封装继承多态 Prototype-based 基于原型的封装继承多态
Part 5 重写与多态
子类在继承父类之后并没有在成员的个数上的增加而是重写了父类的成员的方法
重写需要在父类的成员上标注 virtual并在子类的成员上标注 override 如果父类不添加 virtual子类不添加 override叫做子类对父类成员的隐藏
类成员的隐藏这种用法不常见 重写与隐藏的发生条件函数成员可见需要对子类可见也就是 public 或 protected 的时候才算是可见的签名一致
public 是不仅对子类可见且对其他类可见 protected 是只对子类可见对其他类不可见
多态 Polymorphism当我们用父类变量引用子类实例的时候调用被重写的成员的时候总是能够调用到继承类上最新的成员当调用方法成员的时候能调用到的永远是跟实例类型相关的最新的版本
多态是基于重写机制的virtual - override
下面是有关重写与多态的一个例子
using System;namespace HelloRider
{class Program {static void Main(string[] args){var car new Car();car.Run();// 可以看到输出为 Car is running!var vehicle new Vehicle();vehicle.Run();// 可以看到输出为 Im running!var vehicle2 new RaseCar();vehicle2.Run();// 可以看到输出为 RaceCar is running!// 涉及到多态Vehicle v new Car();v.Run(); // 因为引用的是 Car 类型的实例// 可以看到输出为 Car is running!v.Walking(); // 可以看到输出为 Im walking!// 因为 Car 类型的 Walking 和 Vehicle 类型的 Walking 并没有重写关系// 可以视为这个 Walking 中有两个方法一个属于 Vehicle 一个属于 Car}}class Vehicle{// 重写需要在父类的成员上标注 virtual并在子类的成员上标注 overridepublic virtual void Run(){Console.WriteLine(Im running!);}public void Walking(){Console.WriteLine(Im walking!);}}class Car : Vehicle{// 子类中的方法在父类中也有并没有新增方法只是更新了父类方法的版本// 子类对父类成员的重写也叫做成员的纵向扩展public override void Run(){Console.WriteLine(Car is running!);}// 如果父类不添加 virtual子类不添加 override叫做子类对父类成员的隐藏public void Walking(){Console.WriteLine(Car is walking);}}class RaseCar : Car{// 继承链的重写public override void Run(){Console.WriteLine(RaceCar is running!);}}
}下面这个例子是对属性进行的重写
using System;namespace HelloRider
{class Program {static void Main(string[] args){Vehicle vehicle new Car();vehicle.Run();Console.WriteLine(vehicle.Speed);}}class Vehicle{private int _speed;public virtual int Speed{get { return _speed;}set { this._speed value; }}// 重写需要在父类的成员上标注 virtual并在子类的成员上标注 overridepublic virtual void Run(){Console.WriteLine(Im running!);this._speed 100;}public void Walking(){Console.WriteLine(Im walking!);}}class Car : Vehicle{private int _rpm;public override int Speed{get{return this._rpm / 100;}set{this._rpm value / 100;}}// 子类中的方法在父类中也有并没有新增方法只是更新了父类方法的版本// 子类对父类成员的重写也叫做成员的纵向扩展public override void Run(){Console.WriteLine(Car is running!);this._rpm 5000;}// 如果父类不添加 virtual子类不添加 override叫做子类对父类成员的隐藏public void Walking(){Console.WriteLine(Car is walking);}}class RaseCar : Car{// 继承链的重写public override void Run(){Console.WriteLine(RaceCar is running!);}}
}Section 4 类的扩展知识
接口与抽象类是面向对象设计中最重要的部分是软件工业设计的两块基石 SOLID 原则单一职责原则、开放关闭原则、里氏替换原则、接口隔离原则和依赖倒置原则
Part 1 抽象类
下面的代码解释了什么是抽象类以及开放关闭原则的简单释义
namespace HelloRider
{class Program {static void Main(string[] args){}}// 抽象类指的是函数成员没有被完全实现的类abstract class Student{abstract public void Study(); // 没有被实现的函数成员一定用 abstract 关键字修饰且不能用 private// 被 abstract 修饰的方法只有返回值方法名和参数列表没有方法体是完全没有被实现的方法是抽象方法// 一旦一个类里有抽象方法或其他抽象成员这个类就变成了抽象类抽象类前面必须加上 abstract// 因为抽象类含有未被实现的成员因此编译器不允许实例化这个抽象类// 两个作用// 1、作为基类让别人从自己派生出去在派生类实现没有实现的方法// 2、作为基类声明变量用基类类型的变量去引用子类类型的实例}}/** 为做基类而生的“抽象类”与“开放/关闭原则”* * 开闭原则我们应该封装一些不变的、稳定的、固定的和确定的成员* 而把那些不确定的、有可能改变的成员声明为抽象成员并留给子类去实现** */下面的例子是为做基类而生的“抽象类”与“开放/关闭原则”
using System;namespace HelloRider
{class Program {static void Main(string[] args){Vehicle v new RaceCar(); // 抽象类的唯一能做的事情就是给别的类当基类并且引用一些已经完全实现抽象成员的子类实例v.Run();}}// 程序既有抽象类也遵守开闭原则abstract class Vehicle{public void Stop(){Console.WriteLine(Stopped!);}public void Fill(){Console.WriteLine(Pay and fill......);}public abstract void Run();}class Car : Vehicle{public override void Run() // 实现抽象方法的时候也需要加上 override{Console.WriteLine(Car is running......);}}class Truck : Vehicle{public override void Run(){Console.WriteLine(Truck is running......);}}class RaceCar : Vehicle{public override void Run(){Console.WriteLine(Race Car is running......);}}
}纯抽象类
using System;namespace HelloRider
{class Program {static void Main(string[] args){Vehicle v new RaceCar(); // 抽象类的唯一能做的事情就是给别的类当基类并且引用一些已经完全实现抽象成员的子类实例v.Run();}}// 纯抽象类在 C# 中实际上就是接口abstract class VehicleBase{abstract public void Stop();abstract public void Fill();abstract public void Run();}// 程序既有抽象类也遵守开闭原则abstract class Vehicle : VehicleBase{public override void Stop(){Console.WriteLine(Stopped!);}public override void Fill(){Console.WriteLine(Pay and fill......);}}class Car : Vehicle{public override void Run() // 实现抽象方法的时候也需要加上 override{Console.WriteLine(Car is running......);}}class Truck : Vehicle{public override void Run(){Console.WriteLine(Truck is running......);}}class RaceCar : Vehicle{public override void Run(){Console.WriteLine(Race Car is running......);}}
}纯抽象类在 C# 中就是接口下面是示例
using System;namespace HelloRider
{class Program {static void Main(string[] args){Vehicle v new RaceCar(); // 抽象类的唯一能做的事情就是给别的类当基类并且引用一些已经完全实现抽象成员的子类实例v.Run();}}// 纯抽象类在 C# 中实际上就是接口interface IVehicle{void Stop();void Fill();void Run();}// 类实现接口// 通过 抽象类作为不完全的实现将其作为基类再创建具体类abstract class Vehicle : IVehicle{public void Stop(){Console.WriteLine(Stopped!);}public void Fill(){Console.WriteLine(Pay and fill......);}abstract public void Run();}class Car : Vehicle{public override void Run() // 实现抽象方法的时候也需要加上 override{Console.WriteLine(Car is running......);}}class Truck : Vehicle{public override void Run(){Console.WriteLine(Truck is running......);}}class RaceCar : Vehicle{public override void Run(){Console.WriteLine(Race Car is running......);}}
}什么是接口和抽象类 接口和抽象类都是软件工程的产物如果不遵循软件工程将提高代码的维护难度 具体类 - 抽象类 - 接口越来越抽象内部实现的东西越来越少 抽象类是未完全实现逻辑的类可以由字段和非public成员它们代表了具体逻辑 抽象类为复用而生专门作为基类来使用也具有解耦功能 封装确定的开放不确定的推迟到合适的子类中去实现 接口是完全未实现逻辑的类纯虚类只有函数成员所有成员都是 public也是隐式 public 接口为解耦而生高内聚低耦合方便单元测试 接口是一个协约早已为工业生产所熟知 它们都不能实例化只能用来声明变量、引用具体类concrete class的实例
Part 2 接口
接口由抽象类进化而来接口中的成员方法必须是 public因此写的时候不需要明写public默认的就是public 接口的本质服务的调用者与服务的提供者之间的契约对双方必须是可见的因此使用 public 在抽象世界中是类与类、对象与对象之间的分工与合作这个合作叫做依赖依赖的同时就出现了耦合依赖越直接耦合就越紧下面就是解释依赖与耦合的例子
namespace HelloRider
{class Program {static void Main(string[] args){var engine new Engine();var car new Car(engine);car.Run(3);Console.WriteLine(car.Speed);// 可以看到输出结果为 30}}class Engine{public int RPM { get; private set; }public void Work(int gas){this.RPM 1000 * gas;}}class Car{// 此时 Car 和 Engine 类型紧耦合在一起Car 依赖在 Engine 上// 紧耦合的问题在于基础的类如果出问题会导致依赖这个类的类出现问题// 紧耦合会导致调试难度升高这就是紧耦合带来的弊端private Engine _engine;public Car(Engine engine){_engine engine;}public int Speed { get; private set; }public void Run(int gas){_engine.Work(gas);this.Speed _engine.RPM / 100;}}
}如何解决紧耦合的问题那就是引入接口下面是例子
namespace HelloRider
{class Program {static void Main(string[] args){var user new PhoneUser(new EricssonPhone()); // 只在这块换了类名而 User class 还有其他的代码都没变动// 引入接口之后耦合变得非常的松松耦合user.UsePhone();}}class PhoneUser{private IPhone _phone;public PhoneUser(IPhone phone){_phone phone;}public void UsePhone(){_phone.Dail();_phone.PickUp();_phone.Send();_phone.Receive();}}interface IPhone{void Dail();void PickUp();void Send();void Receive();}class NokiaPhone : IPhone{public void Dail(){Console.WriteLine(Nokia calling ...);}public void PickUp(){Console.WriteLine(Hello! This is Tim!);}public void Send(){Console.WriteLine(Nokia message ring ...);}public void Receive(){Console.WriteLine(Hello!);}}class EricssonPhone : IPhone{public void Dail(){Console.WriteLine(Ericsson calling ...);}public void PickUp(){Console.WriteLine(Hello! This is Jack!);}public void Send(){Console.WriteLine(Ericsson ring ...);}public void Receive(){Console.WriteLine(Good morning!);}}
}接口是为了松耦合而生、是为了解耦合而生
Part 3 单元测试与依赖倒置原则
单元测试是依赖倒置原则在开发中的直接应用 依赖倒置原则高层模块不应该依赖底层模块二者都应依赖其抽象抽象接口不应该依赖于细节具体功能的实现细节应依赖抽象按接口格式写实现的程序 也就是应针对接口编程不应针对实现编程
namespace HelloRider
{class Program {static void Main(string[] args){var fan new DeskFan(new PowerSupply());Console.WriteLine(fan.Work());}}class PowerSupply{public int GetPower(){return 210;}}class DeskFan{private PowerSupply _powerSupply;public DeskFan(PowerSupply powerSupply){_powerSupply powerSupply;}public string Work(){int power _powerSupply.GetPower();if (power 0){return Wont work.;}else if (power 100){return Slow;}else if (power 200){return Work fine;}else{return Warning!;}}}
}可以看到上面的例子是一个紧耦合的例子当我们想要修改电压 power 的值的时候需要修改 PowerSupply 里面的值。一个类设计好之后是不能够再去直接动类的代码为了解决这个紧耦合的问题引入接口进行解耦并使用单元测试来测试DeskFan下面是修改之后的代码
namespace HelloRider
{class Program {static void Main(string[] args){var fan new DeskFan(new PowerSupply());Console.WriteLine(fan.Work());}}public interface IPowerSupply{int GetPower();}public class PowerSupply : IPowerSupply{public int GetPower(){return 210;}}public class DeskFan{private IPowerSupply _powerSupply; // 所有的耦合类型都变成接口类型public DeskFan(IPowerSupply powerSupply){_powerSupply powerSupply;}public string Work(){int power _powerSupply.GetPower();if (power 0){return Wont work.;}else if (power 100){return Slow;}else if (power 200){return Work fine;}else{return Explode!;}}}
}下面是单元测试例子也是接口在单元测试中的应用
using NUnit.Framework;
using HelloRider;namespace InterfaceExample.Tests
{public class DeskFanTests{[SetUp]public void Setup(){}[Test]public void PowerLowerThanZero_OK() // 测试电压为 0 时的测试结果是否符合预期{var fan new DeskFan(new PowerSupplyLowerThanZero());var expected Wont work.;var actual fan.Work();Assert.AreEqual(expected, actual);}[Test] // 测试例子必须加上 [Test]public void PowerHigherThan200_Warning() // 测试电压超过 200 时的测试结果是否符合预期{var fan new DeskFan(new PowerSupplyHigherThan200());var expected Warning!;var actual fan.Work();Assert.AreEqual(expected, actual);}}class PowerSupplyLowerThanZero : IPowerSupply{public int GetPower(){return 0;}}class PowerSupplyHigherThan200 : IPowerSupply{public int GetPower(){return 220;}}
}上图展示的是单元测试的运行结果可以看到左下角部分一个测试case通过一个测试case没有通过因此可以在出现问题的测试样例部分打断点调试
Part 4 接口隔离原则
违反接口隔离原则带来的后果有两个
例子1
namespace HelloRider
{class Program {static void Main(string[] args){var driver new Driver(new Car());driver.Drive();}}// 当把包含过多功能的接口类型传给功能调用者的时候// 必然有一部分功能是用不到的违反了接口隔离原则// 实现这个接口的类同时违反了单一职责原则// 针对这个问题的解决方案把接口拆分为多个小接口// 本质就是把不同的功能分离开封装成接口class Driver{private IVehicle _vehicle;public Driver(IVehicle vehicle){_vehicle vehicle;}public void Drive(){_vehicle.Run();}}interface IVehicle{void Run();}class Car : IVehicle{public void Run(){Console.WriteLine(Car is running);}}class Truck : IVehicle{public void Run(){Console.WriteLine(Truck is running);}}interface ITank{void Fire();void Run();}class LightTank : ITank{public void Fire(){Console.WriteLine(Boom!);}public void Run(){Console.WriteLine(Light Tank is running);}}class MediumTank : ITank{public void Fire(){Console.WriteLine(Boom!!!);}public void Run(){Console.WriteLine(Medium Tank is running);}}class HeavyTank : ITank{public void Fire(){Console.WriteLine(Boom!!!!!!);}public void Run(){Console.WriteLine(Heavy Tank is running);}}
}如果此时想要传入 ITank 接口需要修改 Driver 类的代码这时问题出在把一个胖接口传了进去有永远用不到的功能 Fire现在这个设计违反了接口隔离原则而胖接口是由两个本质不同的东西合并起来的时候应该把胖接口分裂成两个接口也就是 Fire 和 Run 分离开Fire 属于武器类Run 属于机动车类下面是修改后的代码
namespace HelloRider
{class Program {static void Main(string[] args){var driver new Driver(new Car());var driver new Driver(new Truck());var driver new Driver(new HeavyTank());// 可以看到这个 Driver 只要求能跑修改完代码后// 无论是 Car、Truck还是Tank都可以跑// Driver 只调用 Run不调用其他无关的符合了接口隔离原则driver.Drive();}}// 当把包含过多功能的接口类型传给功能调用者的时候// 必然有一部分功能是用不到的违反了接口隔离原则// 实现这个接口的类同时违反了单一职责原则// 针对这个问题的解决方案把接口拆分为多个小接口// 本质就是把不同的功能分离开封装成接口class Driver{private IVehicle _vehicle;public Driver(IVehicle vehicle){_vehicle vehicle;}public void Drive(){_vehicle.Run();}}interface IVehicle{void Run();}interface IWeapon{void Fire();}class Car : IVehicle{public void Run(){Console.WriteLine(Car is running);}}class Truck : IVehicle{public void Run(){Console.WriteLine(Truck is running);}}interface ITank : IVehicle, IWeapon // 一个接口对多个接口的继承{}class LightTank : ITank{public void Fire(){Console.WriteLine(Boom!);}public void Run(){Console.WriteLine(Light Tank is running);}}class MediumTank : ITank{public void Fire(){Console.WriteLine(Boom!!!);}public void Run(){Console.WriteLine(Medium Tank is running);}}class HeavyTank : ITank{public void Fire(){Console.WriteLine(Boom!!!!!!);}public void Run(){Console.WriteLine(Heavy Tank is running);}}
}过于追求接口隔离原则和单一职责原则的时候会产生很多很细碎的、只有一个方法的接口和类因此一定要掌握平衡把接口和类的大小要维持平衡。
例子2传给调用者的胖接口本身是由两个原本设计很好的小接口合并而来本来应该传进一个小接口结果传进了合并了小接口的大接口进来把原本合格的服务提供者挡在外面下面是例子和修改建议
namespace HelloRider
{class Program {static void Main(string[] args){int[] nums1 {1, 2, 3, 4, 5};ArrayList nums2 new ArrayList {1, 2, 3, 4, 5};Console.WriteLine(Sum(nums1));Console.WriteLine(Sum(nums2));var nums3 new ReadOnlyCollection(nums1);foreach (var n in nums3){Console.WriteLine(n);}Console.WriteLine(Sum(nums3)); // 此时 Sum 函数无法处理 nums3// 虽然现在只用得到迭代但现在传入的使 ICollection// 把一些合格的 Service Provider 挡在外面// 只需要把 Sum 传入的 ICollection 改为 IEnumerable 即可// 这时符合接口隔离原则调用者绝不多要用不着的功能// static int Sum(IEnumerable nums)}static int Sum(ICollection nums){int sum 0;foreach (var num in nums){sum (int) num;}return sum;}}// 只能用于迭代不能添加也不能删除的集合class ReadOnlyCollection : IEnumerable{private int[] _array;public ReadOnlyCollection(int[] array){_array array;}public IEnumerator GetEnumerator(){return new Enumerator(this);}// 成员类public class Enumerator : IEnumerator{private ReadOnlyCollection _collection;private int _head;public Enumerator(ReadOnlyCollection collection){_collection collection;_head -1;}public bool MoveNext(){if (_head _collection._array.Length){return true;}else{return false;}}public void Reset(){_head -1;}public object Current{get{object o _collection._array[_head];return o;}}}}
}例子3专门展示 C# 中特有的显式接口实现
namespace HelloRider
{class Program {static void Main(string[] args){var wk new WarmKiller();wk.Love();wk.Kill();// 此时Love 和 Kill 方法都轻易能被调用var wk2 new WarmKillerAAA();wk2.Love(); // 此时只能看到 Love 看不到 KillerIKiller killer new WarmKillerAAA();killer.Kill(); // 此时才能看到 Killer// 如果此时要用 Love 方法var wk3 killer as WarmKiller; // 强制类型转换wk3.Love(); // 就能看到 Love 方法了}}interface IGentleman{void Love();}interface IKiller{void Kill();}// 普通的接口隔离与普通的类与实现class WarmKiller : IGentleman, IKiller{public void Love(){Console.WriteLine(I will love you for ever ...);}public void Kill(){Console.WriteLine(Let me kill you ...);}}// 普通的接口隔离与普通的类与实现class WarmKillerAAA : IGentleman, IKiller{public void Love(){Console.WriteLine(I will love you for ever ...);}// 只有把这个类的实例当做 IKiller 类型的实例的时候这个方法才能被调用// 也就是 只有 Killer 类型的变量来引用 WarmKillerAAA 类类型的时候这个方法才能被调用void IKiller.Kill() {Console.WriteLine(Let me kill you ...);}}
}Part 5 反射 待补充
反射不是 C# 语言的功能而是 .Net 框架的功能。 下面的例子是反射的基本原理
class Program {static void Main(string[] args){ITank tank new HeavyTank();// // 分割线以下不再使用静态类型// 完全是从内存中读取出动态的类型的描述MetaData 和方法的表述// 这块展示的使反射的基本原理var t tank.GetType();object o Activator.CreateInstance(t);MethodInfo fireMi t.GetMethod(Fire);MethodInfo runMi t.GetMethod(Run);fireMi.Invoke(o, null);runMi.Invoke(o, null);}}class Driver{private IVehicle _vehicle;public Driver(IVehicle vehicle){_vehicle vehicle;}public void Drive(){_vehicle.Run();}}interface IVehicle{void Run();}interface IWeapon{void Fire();}class Car : IVehicle{public void Run(){Console.WriteLine(Car is running);}}class Truck : IVehicle{public void Run(){Console.WriteLine(Truck is running);}}interface ITank : IVehicle, IWeapon // 一个接口对多个接口的继承{}class LightTank : ITank{public void Fire(){Console.WriteLine(Boom!);}public void Run(){Console.WriteLine(Light Tank is running);}}class MediumTank : ITank{public void Fire(){Console.WriteLine(Boom!!!);}public void Run(){Console.WriteLine(Medium Tank is running);}}class HeavyTank : ITank{public void Fire(){Console.WriteLine(Boom!!!!!!);}public void Run(){Console.WriteLine(Heavy Tank is running);}}在实际的工作中大部分接触的都是封装好的反射。 封装好的反射最重要的功能之一是依赖注入 Dependencey InjectionDI
Part 6 泛型 Generic 待补充
泛型无处不在是面向对象编程中与接口相当的位置 为什么需要泛型避免成员膨胀或者类型膨胀 正交性泛型类型泛型成员 泛型的东西在编程的时候是不能直接拿来使用的需要经过特化才能使用 泛型类凡是经过特化之后使用到类型参数的地方都是强类型的 下面的例子是泛型类既不产生类型膨胀也不产生成员膨胀
namespace HelloRider
{class Program {static void Main(string[] args){Apple apple new Apple() {Color Red};BoxApple box1 new BoxApple() {Cargo apple};Console.WriteLine(box1.Cargo.Color);// 可以看到输出为 RedBook book new Book() {Name New Book};BoxBook box2 new BoxBook() {Cargo book};Console.WriteLine(box2.Cargo.Name);// 可以看到输出为 New Book}}class Apple{public string Color { get; set; }}class Book{public string Name { get; set; }}class BoxTCargo // 普通类改成泛型类就是在类名后加上尖括号在尖括号内写上类型参数{public TCargo Cargo { get; set; }}
}下面的例子是泛型接口的例子
namespace HelloRider
{class Program {static void Main(string[] args){Studentint student new Studentint();student.ID 101;student.Name Tim;Teacher teacher new Teacher();teacher.ID 1001;teacher.Name Cheems;}}interface IUniqueTId{public TId ID { get; set; }}// 如果一个类实现的是泛型接口那么这个类本身也是泛型的// 这个类是实现泛型接口所以成了泛型类 {第一个方法}class StudentTId : IUniqueTId{public TId ID { get; set; }public string Name { get; set; }}// 在实现泛型接口的时候是实现特化之后的泛型接口那么这个类就不再是泛型类 {第二个方法}class Teacher : IUniqueulong{public ulong ID { get; set; }public string Name { get; set; }}
}泛型之所以对编程的影响很广泛在 C# 中几乎所有常用的数据结构都是泛型的