C# 基础补充

102 阅读4分钟

事件

事件的模型有五个组成部分:事件拥有者、事件触发、事件响应者、事件处理器、事件订阅

示例代码:最基本的情况
using System;
using System.Timers;

class Program
    {
        static void Main(string[] args)
        {
            System.Timers.Timer timer = new System.Timers.Timer(); //事件拥有者
            Boy boy = new Boy(); //事件响应者
        Grily grily = new Grily();
            timer.Interval = 1000;  //Interval 时间间隔
            timer.Elapsed += boy.Action;
        timer.Elapsed += grily.Action;
            // boy.Action是事件处理器
           //其中事件订阅是 这个+=操作符
             timer.Start();
        Console.ReadLine();
        }

    }
class Boy
{
    internal void Action(object? sender, ElapsedEventArgs e)
    //他上面这句object? sender, ElapsedEventArgs e 是用来判断对象是否用于事件订阅如果有则进行事件处理
    //事件处理器
    {
        Console.WriteLine("领域展开");
    }
}

class Grily
{
    internal void Action(object? sender, ElapsedEventArgs e)
    {
        Console.WriteLine("无量空处");

    }
}

小总结:其中 Boy和Grily受到事件响应所触发,timer是事件拥有者本身、通过 +=来去对事件进行订阅

第一种情况

通过事件的响应者去订阅事件的拥有者:事件响应=>去找到这个响应是那个事件去进行的

namespace chuangti
{
    class Program
    {
        static void Main(string[] args)
        {
            Form form = new Form(); //事件的拥有者
            Constal constal = new Constal(form); //事件的响应者
            form.ShowDialog();

        }
    }
    class Constal
    {
        private Form form;
        public Constal(Form form)
        {
            if (form != null)
            {
                this.form = form;  //这里this所指向的form 是自己定义private 成员变量的form
                this.form.Click += this.FormConstall;
            }
        }

        private void FormConstall(object? sender, EventArgs e)
        {
            this.form.Text = DateTime.Now.ToString();
        }
    }
}

第二种情况

这个对象是事件的拥有者的同时也是事件的响应者

using System;
using System.Windows.Forms;
namespace chuangti
{
    class Program
    {
        static void Main(string[] args)
        {
            //form是事件的拥有者同时也是事件的响应者
            MyForm form = new MyForm();
            form.Click += form.FormClick;
            form.ShowDialog();
        }
    }
    class MyForm : Form
    {
    	
        internal void FormClick(object? sender, EventArgs e)
        {
            this.Text = DateTime.Now.ToString();
        }
    }
}
总结:
在 Main 方法中,创建了一个名为 form 的 MyForm 对象,MyForm 是继承自 Form 的自定义窗体类。这个窗体既是事件的拥有者,也是事件的响应者。
定义了一个名为 MyForm 的类,它继承自 Form 类。在 MyForm 类中,定义了一个名为 FormClick 的方法,该方法是用来处理窗体点击事件的。

第三种也是最常用的

事件的响应者是事件拥有者的一个字段,通过方法去订阅自身成员的某个对象/字段

  public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.button1.Click += BuronClil;
            
        }

        private void BuronClil(object? sender, EventArgs e)
        {
            this.textBox1.Text = "现在是我的事件";
        }
    }
    
   //其实很简单,一个比较基础的逻辑


    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.button1.Click += BuronClil;
            
        }

        private void BuronClil(object? sender, EventArgs e)
        {
            //sender是指事件拥有者 EventArgs e 是事件的响应者
            if(sender == this.button1)
            {
                this.textBox1.Text = "点击的第一个按钮";
            }
        }

事件的完整流程
例子是这样的:顾客来到吃饭的地方,然后小儿过来给顾客进行点菜,小儿这个人只为点菜而生,然后顾客吃完之后进行结账

事件:点菜  
成员:事件的拥有者、事件的响应者。 小儿只为点菜,所以是事件的拥有者、顾客因为小儿过来,所以才进行点菜,这是事件的响应者。
`事件是基于委托的` :就是事件的返回值参数类型是与委托所定义的参数类型是一致的。 而且事件的响应者提供给事件拥有者适合的处理,需要找一个地方对这个处理进行一个存储

上代码:

using System;
namespace shijain
{
    class Program
    {
        static void Main(string[] args)
        {
            Custorm custorm = new Custorm(); //事件的拥有者
            Watier watier = new Watier();  //事件的响应者
            custorm.Order += watier.Action; //进行一个事件的订阅 Action是自定义方法名称 //事件的处理器 事件的订阅
            //现在事件5部分已经有就四个,现在就缺少事件的触发

            custorm.Action();
            custorm.PlayBill();

        }
    }

    //小二点餐的事件
    public class OrderEventArgs : EventArgs
    {
        public string BillName { get; set; }
        public string BillSize { get; set; }

    }

    //现在事件的拥有者和事件的响应者都有了,那么就需要创建事件,但是事件通常都是带上委托的
    //所以创建事件之前必须要自定义一个委托进行对事件的类型限制
    public delegate void OrderEventHandler(Custorm custorm, OrderEventArgs e);


    //点餐得到的总价格
    public class Custorm
    {
        //委托创建好了,就要创建一个委托的类型字段,用来引用和处理事件的
        private OrderEventHandler orderEventHandler;
        //创建事件,事件的类型就是上方定义的类型字段
        public event OrderEventHandler Order; //其中event就是创建事件的关键字

        public double Bill { set; get; }
        public void PlayBill()
        {
            Console.WriteLine("这顿饭一共多少钱:" + Bill);
        }
       
       //事件的触发
        public void Think()
        {
            for(int i = 0; i<=5; i++)
            {
                Console.WriteLine("让我想想");
                Thread.Sleep(1000);
            }
            if (this.Order != null)
            {
                OrderEventArgs e = new OrderEventArgs();
                e.BillName = "干炒牛河";
                e.BillSize = "large";
                this.Order(this, e);
            }
        }
        public void Action()
        {
            Console.ReadLine();
            this.Think();
        }
    }

    //创建一个事件的响应者
    class Watier
    {
        public void Action(Custorm custorm, OrderEventArgs e)
        {
            Console.WriteLine("我想吃:" + e.BillName + "" + e.BillSize);
            double price = 10;
            switch (e.BillSize)
            {
                case "small":
                    price = price * 0.5;
                    break;
                case "large":
                    price = price * 1.5;
                    break;
                default:
                    break;
            }
            custorm.Bill += price;
        }
    }
}

总结:

​ 1、从上方的代码我们知道,在创建事件之前,都会创建一个委托和一个委托字段,用来去当作事件的类型限制,所以创建事件之前,必须跟上委托。

​ 2、事件的拥有者和事件的订阅者,也没什么好说的,创建实例之后通过+=来去对事件进行订阅

​ 3、通过+= 订阅的方法:+= watier.Action Action就是充当事件的处理器

​ 4、 OrderEventArgs : EventArgs 当触发时候需要传递一些额外的参数给事件处理器的时候,通常都会去继承EventArgs 虽然EventArgs可有可无,但是可以作为一个编程习惯去进行

​ 5、关于事件:事件在定义的时候通常都会有add 和remove这2个方法 但是如果我们手动去进行方法创建,那么到时候访问的是我们手动的创建的那个名称 不是方法名称 看代码

   public event OrderEventHandler Order
   {
   	add{ this.orderEventHandler += value}
   	remove{this.orderEventHnadler = value}
   }
   //那么这个时候如果要去访问这个方法 那么访问的格式是
   this.orderEventHandler   //而不是访问Order
   
   public event OrderEventHandler Order;
   //当我们通过这种方法去进行简写 那么访问的时候是直接访问方法名称
   this.Order
   
   //一个语法糖

6、为什么有了委托字段/熟悉之后,还要需要事件:这是为了让程序更加的有逻辑性。其实事件的本身就是委托字段的一个包装器

改写和多态

总结: 反正多态:就是看你实例化的是那一部分你所调用是看那一部分 但是问题来了,如果只是简单的重写那么 在子类和父类之间是不需要去通过 virtual override关键字去进行改写 但是如果进行多态操作,那么就需要在拥有这2个关键字(虚方法)的前提下才能执行,不然实例化的东西无效

namespace Pver
{
    class Program
    {
        static void Main(string[] args)
        {
            Vehi v = new Car();
            v.Run(); //Car Running---
            Vehi a = new Vehi();
            Car b = new Car();
            a.Run(); //Running————
            b.Run(); //Car Running---
        }
    }

    class Vehi
    {
        public  virtual void Run()
        {
            Console.WriteLine("Running————");
        }
    }
    class Car : Vehi
    {
        public override void Run()
        {
            Console.WriteLine("Car Running---");
        }
    }
}

抽象类

1、抽象类是一个没有方法体的方法,主要是用来被其他东西继承然后进行改写的一种桥梁

2、看下方代码,Run方法是需要进行一个重写的,那么为什么我们不直接把Vehicle 中的run方法的方法体清空,去置空一个抽象方法,然后等后续添加类的时候,直接去进行对run方法的重写

3、抽象类是无法进行实例化的,反正抽象类/抽象方法,就是一个给别人当父类的一种东西

namespace go
{
    class Program
    {
        static void Main(string[] args)
        {
            Vehicle v = new Car();
            v.run();
            v.stop();
            //  Vehicle v = new Vehicle(); 无法实现
        }
    }

   abstract class Vehicle
    {
        public  void stop()
        {
            Console.WriteLine("Stopping");
        }

        //public virtual void run()
        //{
           // Console.WriteLine("Vehicle Running");
        //}
        
        public abstract void run();
    }
    class Car : Vehicle
    {
        public override void run()
        {
            Console.WriteLine("Car running");
        }
    }
    class NewCar:Vehicle
    {
    //当我们去进行继承的时候,编译器已经直接提示我们实现这个抽象类方法了
       public override void run()
        {
            Console.WriteLine("使用抽象类实现的");
        }
    }

接口与单元测试

接口是抽象类进化而来的,接口中的抽象方法,必须都是public

总结:接口它相当于就是一种契约,它定义了一组方法、属性和事件,但不提供实现细节。接口提供了一种规范,用于指定类应该具备什么样的行为或功能,通过关键字interface去声明,接口内容包含方法、属性、事件和索引器的声明。

接口代码案例:

   class Program
    {
      static void Main(string[] args)
        {
            var user = new PhoneUser(new Nokia());
            //至于为什么使用var来去声明对象,那是因为var可以是任意类型
            user.UserPhone();

        }
    }

   class PhoneUser
    {
        //这个就是一个构造器
        private Iphone _phone;
        public PhoneUser(Iphone iphone) {
            _phone = iphone;
        }
        
        public void UserPhone()
        {
            _phone.Dail();
            _phone.Receive();
            _phone.Pick();
            _phone.Send();
        }
    }


    interface Iphone
    //通过接口定义了几个还为实现的方法
    {
        void Dail();
        void Pick();
        void Send();
        void Receive();
    }

    class Nokia : Iphone
    //然后通过继承来去对接口内的方法去进行一个重写,相当于抽象类
    {
        public void Dail()
        {
            Console.WriteLine("Calling...");
        }

        public void Pick()
        {
            Console.WriteLine("Hello Time...");
        }

        public void Receive()
        {
            Console.WriteLine("Message...");
        }

        public void Send()
        {
            Console.WriteLine("Hello...");
        }
    }

接口隔离

接口隔离原则:一个比较理论的东西,每个接口应该只包含客户端所需的方法,避免定义过于庞大的、臃肿的接口。就是宁愿分多几个接口去处理,都不要让一个接口去处理所有的事情

 class Program
    {
        static void Main(string[] args)
        {
            //var dit = new Drive(new Car());
            var dit = new Drive(new Tank()); //调用的是Tank继承接口的Run方法
            //至于为什么Tank也可以使用,那是因为我们Truck接口继承的是CarM的接口
            //Tank实现Truck接口中的Run其实就是实现CarM接口里面的Run方法
            dit.Driver();
        }
    }
    class Drive
    {
        private CarM a1;
        public Drive(CarM a2) {a1 = a2;}
        public void Driver(){a1.Run(); }
    }

    interface CarM{void Run();}
    
    class Car : CarM
    {
        public void Run()
        {
            Console.WriteLine("现在汽车开始奔跑");
        }
    }

    interface Truck :CarM,a1
    {
        //通过a1这个接口把Fire这个方法分出去了,剩下Run这个方法,因为跟我们上面CarM接口的Run方法差不多所以直接继承过来重写一下就好了
        //接口的多个继承
        //void Fire(); 
        //void Run();
    }
    
    //现在通过接口隔离原则去处理Truck这个接口
    interface a1{void Fire();}
    class Tank : Truck
    {
        public void Fire()
        {
            Console.WriteLine("坦克开火!");
        }
        public void Run()
        {
            Console.WriteLine("现在是继承过来然后进行重写的,坦克快跑!!!");
        }
    }
接口隔离的第二种情况
using Microsoft.VisualBasic;
using System;
using System.Collections;

namespace dsio
{
  class Program
    {
        static  void Main(string[] args)
        {
            var Gite = new WarmKiller();
            Gite.love();
            //Gite.kill();
            //这种是正常的接口隔离写法,但是这种写法是任何只要是WarmKiller实例化的变量都可以进行调用接口方法
            //但是另外一种情况是,必须要是调用某一个指定变量才会看到其中的方法,相当于多一层保密性

            //因为我下面给kill这个方法加上的一个保密,所以如果没有对应的条件是无法直接调用kill这个方法的
            Killer wx = new WarmKiller();
            wx.kill();
            //通过拥有Killer这个类型才能使用kill这个方法
            //如果想要wx 也可以使用love这个方法,可以通过类型转换去实现
            var man = (WarmKiller)wx;
            man.love();

            
            
        }
    }

    interface Killer {void kill();}

    interface Maner { void love(); }

    class WarmKiller : Maner,Killer
    {
        public void love()
        {
            Console.WriteLine("I will love for every one");
        }

        //public void kill()
        //{
        //    Console.WriteLine("I will kill for every man and womeram");
        //}
        //上面这种是普通的写法
      	void Killer.kill()
        {
            Console.WriteLine("必须要又Killer这个变量才可以使用这个方法");
        }

    }
}

反射

反射是指在运行时动态地获取、检查和操作程序的类型、成员(如字段、属性、方法)以及调用方法等。它使我们能够在编译时未知类型的情况下,通过元数据信息对代码进行操作和探索。

反射的基本原理可以分为以下几个步骤:

​ 1、首先要去获取类型信息: 通过GetType()去获取需要进行操作的类型的元信息

​ 2、拿到了信息之后,保险起见可以通过一个对象去对这些信息进行一个保存 object

​ 3、创建实例或调用方法:通过Activator.CreateInstance()方法根据类型对象创建实例

​ 4、使用MethodInfo.Invoke() 方法可以调用方法。 ​ 5、操作字段和属性:通过 FieldInfo.GetValue()FieldInfo.SetValue() 方法可以读取和设置字段的值,通过 PropertyInfo.GetValue()PropertyInfo.SetValue() 方法可以读取和设置属性的值。

using System.Reflection;

namespace fanshe
{
    class Program
    {
        static void Main(string[] args)
        {
            ITank tank = new HevveTank();

            //通过GetType获取到tank对象的信息
            var t = tank.GetType();
            object o = Activator.CreateInstance(t);

            //Activator.CreateInstance 是一个反射相关的静态方法,用于在运行时动态创建对象实例
            //根据给定的类型信息创建该类型的对象,并返回这个对象

            MethodInfo fireMi = t.GetMethod("Fire");
            MethodInfo runMi = t.GetMethod("Run");
            //  MethodInfo 是一个表示方法数据信息的类,通过它可以获取到方法的名称、参数、返回值等


            fireMi.Invoke(o, null);
            runMi.Invoke(o, null);

            //对于反射的调用,它的第一个参数是反射的对象,第二个是是否要给予参数,如果没有那么就是null

        }
    }

    interface ITank
    {
         void Run();
        void Fire();
    }

    class HevveTank:ITank
    {
        public void Run()
        {
            Console.WriteLine("Running....");
        }

        public void Fire()
        {
            Console.WriteLine("Boom.....");
        }
    }
}
依赖注入

依赖反转: 依赖反转就是,将服务方提供的服务,使用方使用的前提条件都分别抽象到两个不同的接口里,从本来使用者到服务者由上而下的依赖关系,转化为分属于两个接口的具体实现

依赖注入它是依赖.net中 下方图片中的框架image-20230815164514292转存失败,建议直接上传图片文件

在依赖注入中,有三个主要角色:

  1. 依赖:组件所需的外部对象或服务,可以是其他类、接口、配置数据等。
  2. 客户端:需要使用依赖的组件或类。
  3. 注入器/容器:负责创建和管理依赖关系的对象。它会在客户端需要依赖时将其注入到客户端中。
 依赖注入的基本用法:
using Microsoft.Extensions.DependencyInjection;
using System;

namespace dsjia
{
    class Program
    {
        static void Main(string[] args)
        {
            var sc = new ServiceCollection(); // 依赖注入首先是创建一个容器
            sc.AddScoped<CarM, Car>(); // 注册 CarM 接口到 Car 类的实现
            sc.AddScoped<Truck, SmallTank>(); // 注册 Truck 接口到 Tank 类的实现
          //  sc.AddScoped<Truck, Tank>();
            //反正就是想要去实现那个类之间去修改类的名称上去就可以了
            var sp = sc.BuildServiceProvider();

            CarM car = sp.GetService<CarM>(); // 获取 CarM 的实例
            car.Run();

            Truck truck = sp.GetService<Truck>(); // 获取 Truck 的实例
            truck.Fire();
            truck.Run();
        }
    }

    class Drive
    {
        private CarM car;
        public Drive(CarM car)
        {
            this.car = car;
        }
        public void Driver()
        {
            car.Run();
        }
    }
    
    interface CarM
    {
        void Run();
    }
    
    class Car : CarM
    {
        public void Run()
        {
            Console.WriteLine("现在汽车开始奔跑");
        }
    }
    
    interface Truck : CarM
    {
        void Fire();
    }
    
    class Tank : Truck
    {
        public void Fire()
        {
            Console.WriteLine("坦克开火!");
        }
        public void Run()
        {
            Console.WriteLine("现在是继承过来然后进行重写的,坦克快跑!!!");
        }
    }

    class SmallTank : Truck
    {
        public void Fire()
        {
            Console.WriteLine("小坦克 boom...");
        }

        public void Run()
        {
            Console.WriteLine("小坦克 Run...");
        }
    }
}

    
    1、首先是创建了一个ServiceCollection()的容器,你没有容器的话那么依赖注入往哪里注入呢
    2、调用 sc.AddScoped<>(),注册Truck接口到Car类实现
    3、使用 sc.BuildServiceProvider() 构建了一个 ServiceProvider 的实例 sp,该实例是实际进行依赖解析和注入的容器。
    4、调用 sp.GetService<Tank>(),通过容器获取了一个 Tank 的实例,并赋值给 tank 变量。
	5、调用实例Tank类中的方法