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

news2024/9/21 2:30:37

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(); // 获取产品,将产品装入 Box
            box.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(); // 获取产品,将产品装入 Box
            if (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 合并到 action1
            action1 += 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(); // 事件拥有者 timer
            timer.Interval = 1000; // ms
            Boy 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(); // 事件的拥有者 form
            Controller controller = new Controller(form); // 事件的响应者 controller
            form.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,事件的响应者也是 from
            form.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;

        // 事件 Order
        public 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.coloradmin.cn/o/932310.html

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

相关文章

图神经网络与分子表征:番外——基组选择

学过高斯软件的人都知道&#xff0c;我们在撰写输入文件 gjf 时需要准备输入【泛函】和【基组】这两个关键词。 【泛函】敲定计算方法&#xff0c;【基组】则类似格点积分中的密度&#xff0c;与计算精度密切相关。 部分研究人员借用高斯中的一系列基组去包装输入几何信息&am…

快速安装Qt开发环境,克服在线安装慢等问题

自从Qt6之后&#xff0c;QtCreater的安装都需要注册账号&#xff0c;并且使用账号在线安装&#xff0c;继续使用官网的资源站下载的话&#xff0c;会特别的慢&#xff0c;以下是提高在线安装速度的做法。 官网下载很慢&#xff0c;快速安装的方式如下 1、winR,输入CMD&#xff…

深入分析负载均衡情景

本文出现的内核代码来自Linux5.4.28&#xff0c;为了减少篇幅&#xff0c;我们尽量不引用代码&#xff0c;如果有兴趣&#xff0c;读者可以配合代码阅读本文。 一、有几种负载均衡的方式&#xff1f; 整个Linux的负载均衡器有下面的几个类型&#xff1a; 实际上内核的负载均衡…

关于述职答辩的一点思考和总结

公众号&#xff1a;赵侠客 侠客说&#xff1a;优秀人才的四个特征&#xff1a;格局、思路、实干、写作 一、前言 1.1 述职答辩的重要性 公司都会有晋升通道&#xff0c;述职答辩是你想升职加薪除了跳槽以外的必由之路&#xff0c;其重要性对个人发展来说不言而喻&#xff0c…

python的文件操作

前言 打印内容到屏幕 最简单的输出方式是调用print函数&#xff0c;此函数会将你传递的表达式转化成字符串表达式&#xff0c;并将结果写道标准输出中。 读取键盘输入 python提供了两个raw_input和input内置函数从标准输入中读取一行文本&#xff0c;默认的标准输入是键盘。 …

7.接着跑一下triton官方教程

5.Model Ensemble 在此示例中&#xff0c;我们将探索使用模型集成来仅通过单个网络调用在服务器端执行多个模型。这样做的好处是减少了在客户端和服务器之间复制数据的次数&#xff0c;并消除了网络调用固有的一些延迟。 为了说明创建模型集成的过程&#xff0c;我们将重用第…

缺页异常与copy-on-write fork

缺页异常需要什么 当发生缺页异常时&#xff0c;内核需要以下信息才能响应这个异常&#xff1a; 出错的虚拟地址&#xff08;引发缺页异常的源&#xff09; 当一个用户程序触发了缺页异常&#xff0c;会切换到内核空间&#xff0c;将出错的地址放到STVAL寄存器中&#xff0c;…

JVM工具-1. jps:虚拟机进程状态工具

文章目录 1. jps介绍2. jps命令格式3. jps工具主要选项4. jps -q5. jps -m6. jps -l7. jps -v 1. jps介绍 jps(JVM Process Status Tool)&#xff1a;虚拟机进程状态工具&#xff0c;可以列出正在运行的虚拟机进程&#xff0c;并显示虚拟机执行主类&#xff08;Main Class&…

3.3.2:SUM作为一般函数及聚合函数的应用

【分享成果&#xff0c;随喜正能量】我们很多道友没受过什么苦&#xff0c;或受不了一句话、一点气&#xff0c;总想悠悠自在成佛。或是念上几十部经就想换取什么&#xff0c;法宝是无价的&#xff01;你拿有价来换&#xff0c;不但换不到&#xff0c;还丧失了功德。应当不退初…

springboot整合jdbctemplate教程

这篇文章介绍一下springboot项目整合jdbctemplate的步骤&#xff0c;以及通过jdbctemplate完成数据库的增删改查功能。 目录 第一步&#xff1a;准备数据库 第二步&#xff1a;创建springboot项目 1、创建一个springboot项目并命名为jdbctemplate 2、添加spring-jdbc和项目…

探讨uniapp的路由与页面生命周期问题

1 首先我们引入页面路由 2 页面生命周期函数 onLoad() {console.log(页面加载)},onShow() {console.log(页面显示)},onReady(){console.log(页面初次显示)},onHide() {console.log(页面隐藏)},onUnload() {console.log(页面卸载)},onBackPress(){console.log(页面返回)}3 页面…

一串神奇的字符,就能让ChatGPT在内的AI聊天机器人变得不正常

一组看似随机的字符被添加到对话提示的末尾&#xff0c;就会发现几乎任何聊天机器人都显露了邪恶本性。 卡内基梅隆大学计算机科学教授Zico Kolter和博士生Andy Zou的一份报告&#xff0c;揭示了当前主流的聊天机器人&#xff0c;特别是ChatGPT&#xff0c;以及Bard、Claude等…

python print ljust 文本对齐打印 对齐打印名册

背景 在python部分场景下&#xff0c;我们需要打印输出一些文本消息&#xff0c;但我们又无法预测可能的打印内容是什么。这种情况下&#xff0c;我们要对齐打印这些文本&#xff0c;是比较比较难以处理的。 例如下面是一列姓名&#xff0c;和对应的一列手机/电话号&#xff0…

自然对数底e的一些事

自然对数底e的一些事 走的人多了就成了路 中国清代数学家李善兰&#xff08;1811—1882&#xff09; 凡此变数中函彼变数者&#xff0c;则此为彼之函数 自然对数底也是使用习惯 &#x1f349; 李善兰把function翻译为函数&#xff0c;函就是包含&#xff0c;含有变量&#xff…

C# Winfrom通过COM接口访问和控制Excel应用程序,将Excel数据导入DataGridView

1.首先要创建xlsx文件 2.在Com中添加引用 3. 添加命名空间 using ApExcel Microsoft.Office.Interop.Excel; --这样起个名字方面后面写 4.样例 //点击操作excelDataTable dt new DataTable();string fileName "D:\desktop\tmp\test.xlsx";ApExcel.Application exA…

【学习FreeRTOS】第20章——FreeRTOS内存管理

1.FreeRTOS内存管理简介 在使用 FreeRTOS 创建任务、队列、信号量等对象的时&#xff0c;一般都提供了两种方法&#xff1a; 动态方法创建&#xff1a;自动地从FreeRTOS管理的内存堆中申请创建对象所需的内存&#xff0c;并且在对象删除后&#xff0c;可将这块内存释放回Free…

牛客练习赛 114

C.Kevin的七彩旗 思路&#xff1a;贪心和dp均可以解决。 贪心&#xff1a;我们可以发现&#xff0c;最终想要获得合法的序列&#xff0c;我们必须是通过把几段连续的序列拼凑起来&#xff0c;但序列之间可能有重合&#xff0c;因此我们就转化为了&#xff0c;记录每一段最大的…

IP编址数据转发(md版)

IP编址&数据转发 一、IP编址1.1、二进制、十进制和十六进制1.2、进制之间的转换1.3、IP编址1.4、子网掩码1.5、二进制和十进制转换1.6、IP地址分类1.7、IP地址类型1.8、地址规划 二、VLSM与CIDR2.1、有类IP编址的缺陷2.2、变长子网掩码 VLSM2.3、缺省情况下的掩码2.4、子网…

Redis使用

环境配置 代码实现 Java public CoursePublish getCoursePublishCache(Long courseId){//查询缓存Object jsonObj redisTemplate.opsForValue().get("course:" courseId);if(jsonObj!null){String jsonString jsonObj.toString();System.out.println("从缓…

Linux安装1Panel(官方)

项目简介安装命令 curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sh quick_start.sh 访问地址 查看命令&#xff1a;1pctl user-info 常用命令 Usage:1pctl [COMMAND] [ARGS...]1pctl --helpCommands: status …