C# 学习笔记--个人学习使用 <2>

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 澄清



Chapter 2 比较硬的基础部分


Section 1 委托

什么是委托?

  • 委托 Delegate 是函数指针的升级版
  • Delegate 的意思是,这有一件事情,我不亲自去做,而是交给别人去做,也就是间接地去做;
#include <studio.h>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;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;
}

我们可以看到输出结果如下:

>> 100+200=300
>> 100-200=-100
>> Press any key to continue ...

在这个例子里,是通过函数的名字,来调用,是直接调用

#include <studio.h>typedef 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;
}

我们可以看到输出结果如下:

>> 100+200=300
>> 100-200=-100
>> Press 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(); // 间接调用,简洁的写法// 参数,参数,返回类型Func<int, int, int> func = new Func<int, int, int>(calculator.Add); Func<int, int, int> func2 = new Func<int, 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
-100

Part 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();Func<Product> func1 = new Func<Product>(productFactory.MakePizza);Func<Product> func2 = new Func<Product>(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 ( Func<Product> 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();Func<Product> func1 = new Func<Product>(productFactory.MakePizza);Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);Logger logger = new Logger();Action<Product> log = new Action<Product>(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 ( Func<Product> getProduct, Action<Product> 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();Action<Product> log = new Action<Product>(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, Action<Product> 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的事件是使用接口来实现的;
    • 事件模式是好东西,但是是有缺陷的,如果编写的时候没有约束,程序的逻辑容易混乱,经过长期的总结下来,总结出MVC,MVP,MVVM等架构模式,这些是事件模式更高级、更有效的用法;
    • 日常开发的时候,使用已有事件的机会比较多,自己声明事件的机会比较少;

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 澄清

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/54277.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Luniux】解决Ubuntu外接显示器不显示的问题

Luniux】解决Ubuntu外接显示器不显示的问题 文章目录 Luniux】解决Ubuntu外接显示器不显示的问题1. 检查nvidia显卡驱动是否正常2. 更新驱动3. 检查显示器是否能检测到Reference 1. 检查nvidia显卡驱动是否正常 使用命令行 nvidia-smi来检查显卡驱动是否正常&#xff0c;如果…

持续集成与持续交付:现代软件测试的变革之路

引言 在数字化时代&#xff0c;软件开发的速度和复杂性都在不断增加。为了满足市场的需求&#xff0c;企业需要更快、更高效地交付高质量的软件产品。在这样的背景下&#xff0c;持续集成与持续交付&#xff08;CI/CD&#xff09;成为了软件开发和测试的核心实践。 软件开发的…

论文阅读 The Power of Tiling for Small Object Detection

The Power of Tiling for Small Object Detection Abstract 基于深度神经网络的技术在目标检测和分类方面表现出色。但这些网络在适应移动平台时可能会降低准确性&#xff0c;因为图像分辨率的增加使问题变得更加困难。在低功耗移动设备上实现实时小物体检测一直是监控应用的…

小研究 - Java虚拟机性能及关键技术分析

利用specJVM98和Java Grande Forum Benchmark suite Benchmark集合对SJVM、IntelORP,Kaffe3种Java虚拟机进行系统测试。在对测试结果进行系统分析的基础上&#xff0c;比较了不同JVM实现对性能的影响和JVM中关键模块对JVM性能的影响&#xff0c;并提出了提高JVM性能的一些展望。…

css之文字连续光影特效、动画、scss

文章目录 效果图htmlscsscss 效果图 html <div><span>C</span><span>O</span><span>L</span><span>O</span><span>R</span><span>F</span><span>U</span><span>L</span&…

WOFOST模型与PCSE模型应用

实现作物产量的准确估算对于农田生态系统响应全球变化、可持续发展、科学粮食政策制定、粮食安全维护都至关重要。传统的经验模型、光能利用率模型等估产模型原理简单&#xff0c;数据容易获取&#xff0c;但是作物生长发育非常复杂&#xff0c;中间涉及众多生理生化过程&#…

Java学数据结构(2)——树Tree 二叉树binary tree 二叉查找树 AVL树 树的遍历

目录 引出什么是树Tree&#xff1f;树的实现二叉树binary tree查找树ADT——二叉查找树Binary Search Tree1.contains方法2.findMax和findMin方法3.insert方法4.remove方法&#xff08;复杂&#xff09;二叉查找树的深度 AVL(Adelson-Velskii和Landis)树——平衡条件(balance c…

流处理详解

【今日】 目录 一 Stream接口简介 Optional类 Collectors类 二 数据过滤 1. filter()方法 2.distinct()方法 3.limit()方法 4.skip()方法 三 数据映射 四 数据查找 1. allMatch()方法 2. anyMatch()方法 3. noneMatch()方法 4. findFirst()方法 五 数据收集…

Day43|leetcode 1049.最后一块石头的重量II、494.目标和、474.一和零

leetcode 1049.最后一块石头的重量II 题目链接&#xff1a;1049. 最后一块石头的重量 II - 力扣&#xff08;LeetCode&#xff09; 视频链接&#xff1a;动态规划之背包问题&#xff0c;这个背包最多能装多少&#xff1f;LeetCode&#xff1a;1049.最后一块石头的重量II_哔哩…

date_range()函数--Pandas

1. 函数功能 生成连续的日期时间序列 2. 函数语法 pandas.date_range(startNone, endNone, periodsNone, freqNone, tzNone, normalizeFalse, nameNone, inclusiveboth, *, unitNone, **kwargs)3. 函数参数 参数含义start可选参数&#xff0c;起始日期end可选参数&#xff…

01-Flask-简介及环境准备

Flask-简介及环境准备 前言简介特点Flask 与 Django 的比较环境准备 前言 本篇来介绍下Python的web框架–Flask。 简介 Flask 是一个轻量级的 Web 框架&#xff0c;使用 Python 语言编写&#xff0c;较其他同类型框架更为灵活、轻便且容易上手&#xff0c;小型团队在短时间内…

QtCreator指定Windows Kits版本

先说下事件起因&#xff1a;之前一直在用Qt5.12.6&#xff0b;vs2017在写程序&#xff0c;后面调研了一个开源库Qaterial&#xff0c;但是翻来覆去的编译都有问题&#xff0c;后面升级到了Qt5.15.2&#xff0b;vs2019来进行cmake的编译&#xff0c;搞定了Qaterial&#xff0c;但…

软考A计划-系统集成项目管理工程师-小抄手册(共25章节)-下

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

深度学习经典检测方法的概述

深度学习经典的检测方法 two-stage&#xff08;两阶段&#xff09;&#xff1a;Faster-rcnn Mask-Rcnn系列 两阶段&#xff08;two-stage&#xff09;是指先通过一个区域提取网络&#xff08;region proposal network&#xff0c;RPN&#xff09;生成候选框&#xff0c;再通过…

SLAM十四讲学习笔记 第二期:部分课后实践代码

持续更新.... 前期准备第二讲实验一&#xff1a;简单输出 第五讲任务一&#xff1a;imageBasics&#xff08;Ubuntu配置opencv&#xff09;任务二&#xff1a;双目匹配点云&#xff08;Ubuntu配置pangolin&#xff09;检验部分我认为可以加深对CMake的理解 任务三&#xff1a;r…

pandas数据分析——groupby得到分组后的数据

groupbyagg分组聚合对数据字段进行合并拼接 Pandas怎样实现groupby聚合后字符串列的合并&#xff08;四十&#xff09; groupby得到分组后的数据 pandas—groupby如何得到分组里的数据 date_range补齐缺失日期 在处理时间序列的数据中&#xff0c;有时候会遇到有些日期的数…

springboot源码编译问题

问题一 Could not find artifact org.springframework.boot:spring-boot-starter-parent:pom:2.2.5.RELEASE in nexus-aliyun (http://maven.aliyun.com/nexus/content/groups/public/) 意思是无法在阿里云的镜像仓库中找到资源 解决&#xff1a;将配置的镜像删除即可&#…

STM32 CAN 波特率计算分析

这里写目录标题 前言时钟分析时钟元到BIT 前言 CubeMX中配置CAN波特率的这个界面刚用的时候觉得非常难用&#xff0c;怎么都配置不到想要的波特率。接下来为大家做一下简单的分析。 时钟分析 STM32F4的CAN时钟来自APB1 在如下界面配置&#xff0c;最好配置为1个整一点的数。…

cpolar做一个内网穿透

因为不在公司&#xff0c;需要访问公司的数据库&#xff0c;所以做一个内网穿透 下载安装 下载地址&#xff1a; https://dashboard.cpolar.com/get-started 下载后是个压缩包&#xff0c;解压后傻瓜式安装 操作隧道 安装后打开Cpolar Web UI 登录账号&#xff0c;查看隧…

如何评估分类模型的好坏

如何评估分类模型的好坏 评估分类预测模型的质量&#xff0c;常用一个矩阵、三条曲线和六个指标。 一个矩阵&#xff1a;混淆矩阵&#xff1b;三条曲线&#xff1a;ROC曲线、PR曲线、KS曲线&#xff1b;六个指标&#xff1a;正确率Acc、查全率R、查准率P、F值、AUC、BEP值、KS…