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 

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 

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 = ⋐	

	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;
        }
    }
}

运行上面的代码,可以获得以下的输出:
C# 学习笔记--个人学习使用 <2>_第1张图片

当我们自定义委托的时候,需要注意几点:

  • 委托与所封装的方法必须保持“类型兼容”
  • 声明委托的时候不要放错位置,委托是类,需要声明在名称空间体里面,放错了可能导致运行不了或成为嵌套类;

C# 学习笔记--个人学习使用 <2>_第2张图片
上图可以看到,第一行是委托的声明,下面四行是与之兼容的方法;

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 委托的高级使用

C# 学习笔记--个人学习使用 <2>_第3张图片

多播委托指的是一个委托内部封装了不止一个方法,下面是例子:

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); // 线程暂停一秒钟
            }
        }
    }
}

C# 学习笔记--个人学习使用 <2>_第4张图片
隐式异步调用

  • 异步调用:与同步调用是相对的,
    • 同步:你做完了,我在你的基础上接着做;
    • 异步:咱们两个同时做,也就是各做各的;
  • 同步调用与异步调用的对比
    • 每一个运行的程序是一个进程 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!");
        }
    }
}

上面展示的是一个事件同时有两个事件处理器的时候的样子;


C# 学习笔记--个人学习使用 <2>_第5张图片
上图展示的是标准的事件机制模型,结构清晰,是MVC、MVP等设计模式的雏形;
下面的程序是对这个标准的事件机制模型的解释

namespace WindowsFormsApp1
{
    internal static class Program
    {
        /// 
        /// The main entry point for the application.
        /// 
        [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();
        }
    }
}

C# 学习笔记--个人学习使用 <2>_第6张图片


C# 学习笔记--个人学习使用 <2>_第7张图片
上图展示的是对象用自己的方法订阅、处理自己的事件;
下面的程序是对上图的解释,同时接触到了什么是派生

namespace WindowsFormsApp1
{
    internal static class Program
    {
        /// 
        /// The main entry point for the application.
        /// 
        [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();
        }
    }
}

C# 学习笔记--个人学习使用 <2>_第8张图片


C# 学习笔记--个人学习使用 <2>_第9张图片
上图展示的是使用最多的,特点是,事件的拥有者是事件的响应者的字段成员,是Windows上默认的事件订阅和处理结构;
下面的程序是对上图示例的解释

namespace WindowsFormsApp1
{
    internal static class Program
    {
        /// 
        /// The main entry point for the application.
        /// 
        [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";
        }
    }
}

C# 学习笔记--个人学习使用 <2>_第10张图片

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

你可能感兴趣的:(C#,c#,学习,笔记)