草庐IT

C#事件详解

鲤籽鲲 2023-05-21 原文

文章目录


前言

在日常开发业务中会经常使用到事件,本文将通过一个个实例讲解事件是什么,事件如何使用。


一、事件是什么?

(1) 事件是一种特殊的委托,本质上来讲事件就是委托。
(2)如果说委托是对方法的包装,那么事件就是对委托进一步的包装,提升了使用的安全性,事件是安全版的委托

理解这个很重要】为何说事件是安全版的委托呢?


1 委托可以定义在一个类的外部也可以定义在内部,任何地方都可以声明委托,对委托进行添加或移除方法和调用等操作。使用的时候是很方便,但是安全性就难以保证。
比如:
在一个类中调用委托的时候本想做指定的事情,但是在另外一个类被添加了新的方法,或者被赋值为null,那么本想做的事情就遭到了破坏,委托好用,但是太过于”开放“了,那么有了代码的安全性问题。


*2 事件的使用规定:事件只能在当前类被访问,子类和类外部均不能执行类中的事件方法
解析:
a.外部类不可能使用xxxEvent() 或 xxxEvent.Invoke()的方式去调用事件
b.事件只能在当前类被调用,外部要使用事件,要先实例化当前类才可以,然后当前类定义一个调用事件的方法。外部通过实例化当前类,然后调用类中方法的方式,调用事件了。
c 外部类只能通过+=或者-=的方式对事件进行添加和移除方法的操作,虽然能添加移除方法,但是调用权在当前类
通过以上3点足以说明事件的安全性

二、事件的使用

案例:猫叫了,老鼠听见了就跑…

    //【1】声明委托,委托声明一般是以EventHandlder结尾的方式命名
    public delegate void CatMiaoEventHanlder();
    public class Cat
    {
        //【2】声明一个事件,--只能在类内部声明
        public event CatMiaoEventHanlder CatMiaoEvent;
        //           CatMiaoEventHanlder CatMiaoEventHanlder; 
        //  这里注意比对申明委托的变量,事件和委托,
        //  只不过是新增了一个event关键字,表示他是一个特殊的委托=》事件

        //【3】-1 定义对应事件的方法,后面需要注册到事件中
        public void Miao()
        {
            Console.WriteLine("喵,喵,喵,喵!");
        }

        //【4】类内部定义一个调用事件的方法
        public void MiaoEvent()
        {
            CatMiaoEvent?.Invoke();
        }
    }

    public class Mouse
    {
        //【3】-2 定义对应事件的方法,后面需要注册到事件中
        public void Run()
        {
            Console.WriteLine("赶紧跑,猫猫来了!");
        }
    }
        static void Main(string[] args)
        {
            //【5】调用事件,完成猫叫后,小老鼠就跑的这一事件
            Cat cat = new Cat();
            cat.CatMiaoEvent += cat.Miao;
            cat.CatMiaoEvent += new Mouse().Run;
            cat.CatMiaoEvent += () => { Console.WriteLine("狗也跟着叫了"); };//临时加一个听见猫叫狗也叫的动作
            cat.MiaoEvent();//最后调用,完成一系列动作
         }

通过以上可知:使用事件有
(1)申明委托
(2)在当前类针对委托定义事件
(3)在需要与事件有关联的类中 编写事件对应的方法,提供后续调用
(例如猫叫了,老鼠听见了需要跑,这就是关联,需要在老鼠的类中写跑的方法)
(4)在当前类中定义调用事件的方法,供外部类调用
(5)在外部调用,实例化当前类,注册与事件相关的方法,然后调用事件,完成一系列动作

三、 WinForm中的事件

关于WinForm中的事件我们需要了解以下内容:
在WinForm当我们双击Button会在后台创建了一个的Button_Click事件,

this.button1.Click += new System.EventHandler(this.button1_Click);

什么是EventHandler呢?

public delegate void EventHandler(object sender, EventArgs e);

其实就是一个系统已经定义好的委托

对于WinForm来说,这个object sender 传递过来的是什么呢?EventArgs又是什么呢?

当前是Button的事件,那么传入进来的object sender 就是Button自身,如果是Form窗体的事件,那么sender 这个就是窗体本身
EventArgs就是与这个事件相关的一些参数,比如位置啊,点击次数啊等等,该参数可以为null

四、EventHandler详解&订阅模式

public delegate void EventHandler(object sender, EventArgs e);

EventHandler就是系统已经定义好的事件(或叫委托),如果我们使用自定义的事件(或叫委托)是不需深究object 和EventArgs这两个入参的,反之,需要使用,都得好好深究一下。
首先我们需要知道,EventArgs是事件参数的基类,我们使用EventHandler事件的时候,如果不需要传递参数,这个可以直接设为null,但是如果需要传入参数,那么传入参数须是EventArgs类型或者EventArgs的派生类

  class Program
    {
        static void Main(string[] args)
        {
            Publisher publisher = new Publisher() { Name="李发布"};
            publisher.PublishEvent += new Subscriber1() { Name = "张订阅" }.Action;
            publisher.PublishEvent += new Subscriber2() { Name = "王订阅" }.Action2;
            publisher.OnPublishEvent(new Book() { Name="平凡的世界"});
            Console.ReadLine();
        }
    }

    //事件发布者
    public class Publisher
    {
        public string Name { get; set; }
        //定义事件
        public event EventHandler<Book> PublishEvent;

        //定义调用事件的方法
        public void OnPublishEvent(Book book)
        {
            //如果实际处理的业务中,不需要入参,object sender 和EventArg e 都可以传入null
            PublishEvent?.Invoke(this,book);
        }
    }
    //事件订阅者1
    public class Subscriber1
    {
        public string Name { get; set; }

        //定义 订阅方法,当触发发布事件的时候就调用该方法
        public void Action(object sender,Book book)
        {
            if (sender is Publisher publisher)
            {
                Console.WriteLine($"发布者:{publisher.Name},订阅者{Name}:订阅了{book.Name}");
            }
        }
    }
    //事件订阅者2
    public class Subscriber2
    {
        public string Name { get; set; }
        public void Action2(object sender, Book book)
        {
            if (sender is Publisher publisher)
            {
                Console.WriteLine($"发布者:{publisher.Name},订阅者{Name}:订阅了{book.Name}");
            }
        }
    }

    //EventArgs是EventHandler中传入参数的基类,因此需要传递参数的时候,参数都需要继承自EventArgs
    public class Book:EventArgs
    {
        public string Name { get; set; }
    }

五、综合案例

需求:汽车出停车场时收费,开闸放行

class Program
    {
        static void Main(string[] args)
        {
            //先分析需求:车到车库门口,摄像机要拍照到车牌后,收费员收费,闸机抬杆
            Camera camera = new Camera();
            Charger charger = new Charger();
            Gate gate = new Gate();
            camera.OnSnapLicenseEvent += charger.Charge;
            camera.OnSnapLicenseEvent += gate.OpenGate;
            camera.SnapPhoto();
            Console.ReadLine();
        }
    }

    public class Camera
    {
        //vs版本较低,无法使用Task,这里使用thread
        public event EventHandler<CarInfo> OnSnapLicenseEvent;

        //模拟摄像机一致在抓拍车牌
        public void SnapPhoto()
        {
            Thread thread = new Thread(() =>
            {
                List<string> licenses = new List<string>()
                 { "", "", "", "沪A11111", "沪B22222","沪C33333","","" };
                Random random = new Random();
                while (true)
                {
                    Thread.Sleep(1000);
                    int index = random.Next(1, licenses.Count + 1);
                    SnapInfo snapInfo = new SnapInfo() { LicensePlate = licenses[index-1] };
                    //当车牌不为空的时候表示车来了
                    if (!string.IsNullOrEmpty(snapInfo.LicensePlate))
                    {
                        Console.WriteLine($"抓拍到车牌{snapInfo.LicensePlate}!");  
                        
                        OnSnapLicense(GetCarInfoBySnapInfo(snapInfo));
                    }
                    else
                    {
                        Console.WriteLine("当前没有抓拍到车牌!");
                        Console.WriteLine("--------------------------------------");
                    }

                }
            });
            thread.IsBackground = true;
            thread.Start();
        }

        public CarInfo GetCarInfoBySnapInfo(SnapInfo snapInfo)
        {
            //抓拍到车牌后,这里直接赋值,相当于模拟通过接口车牌查询了该车的进场数据
            CarInfo carInfo = new CarInfo()
            {
                StartTime = DateTime.Parse("2022-09-09 12:00:00"),
                EndTime = DateTime.Now,
                LicensePlate = snapInfo.LicensePlate,
            };
            return carInfo;
        }

        public void OnSnapLicense(CarInfo carInfo)
        {
            OnSnapLicenseEvent?.Invoke(this, carInfo);
        }

    }

    public class SnapInfo
    {
        //车牌,拍到车牌表示车来了,没拍到车牌,表示没有车
        public string LicensePlate { get; set; }
    }


    public class CarInfo : EventArgs
    {
        //停车的开始时间
        public DateTime StartTime { get; set; }
        //停车的结束时间
        public DateTime EndTime { get; set; }
        //车牌号
        public string LicensePlate { get; set; }
    }

    //收费员(负责收费)
    public class Charger
    {
        //收费
        public void Charge(object sender, CarInfo carInfo)
        {
            Console.WriteLine($"收费员:对{carInfo.LicensePlate}完成了收费");
        }
    }

    //闸机(负责开关)
    public class Gate
    {
        public void OpenGate(object sender, CarInfo carInfo)
        {
            Console.WriteLine($"闸机:放行{carInfo.LicensePlate}");
            Console.WriteLine("--------------------------------------");
        }
    }

测试结果


总结

以上就是今天要讲的内容,事件为我们的编程提供了很大的方便,熟练的使用事件也是每个C#开发人员必备的知识,希望这些讲解能够帮助你加深对事件的理解。


参考:基于自定义事件EventArgs实现发布订阅模式

有关C#事件详解的更多相关文章

  1. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  2. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  3. ruby-on-rails - 事件管理员日期过滤器日期格式自定义 - 2

    是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s

  4. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  5. ruby-on-rails - 事件记录 : Select max of limit - 2

    我正在尝试将以下SQL查询转换为ActiveRecord,它正在融化我的大脑。deletefromtablewhereid有什么想法吗?我想做的是限制表中的行数。所以,我想删除少于最近10个条目的所有内容。编辑:通过结合以下几个答案找到了解决方案。Temperature.where('id这给我留下了最新的10个条目。 最佳答案 从您的SQL来看,您似乎想要从表中删除前10条记录。我相信到目前为止的大多数答案都会如此。这里有两个额外的选择:基于MurifoX的版本:Table.where(:id=>Table.order(:id).

  6. ruby-on-rails - 事件管理员和自定义方法 - 2

    这是我在ActiveAdmin中的自定义页面ActiveAdmin.register_page"Settings"doaction_itemdolink_to('Importprojects','settings/importprojects')endcontentdopara"Text"endcontrollerdodefimportprojectssystem"rakedataspider:import_projects_ninja"para"OK"endendend我想做的是,当我单击“导入项目”按钮时,我想在Controller中执行rake任务。但是我无法访问该方法。可能是什

  7. ruby-on-rails - 在不重新查询数据库的情况下重新排序 Rails 中的事件记录? - 2

    例如,假设我有一个名为Products的模型,并且在ProductsController中,我有以下代码用于product_listView以显示已排序的产品。@products=Product.order(params[:order_by])让我们想象一下,在product_listView中,用户可以使用下拉菜单按价格、评级、重量等进行排序。数据库中的产品不会经常更改。我很难理解的是,每次用户选择新的order_by过滤器时,rails是否必须查询,或者rails是否能够以某种方式缓存事件记录以在服务器端重新排序?有没有一种方法可以编写它,以便在用户排序时rails不会重新查询结果

  8. ruby-on-rails - Ruby 长时间运行的进程对队列事件使用react - 2

    我有一个将某些事件写入队列的Rails3应用。现在我想在服务器上创建一个服务,每x秒轮询一次队列,并按计划执行其他任务。除了创建ruby​​脚本并通过cron作业运行它之外,还有其他稳定的替代方案吗? 最佳答案 尽管启动基于Rails的持久任务是一种选择,但您可能希望查看更有序的系统,例如delayed_job或Starling管理您的工作量。我建议不要在cron中运行某些东西,因为启动整个Rails堆栈的开销可能很大。每隔几秒运行一次它是不切实际的,因为Rails上的启动时间通常为5-15秒,具体取决于您的硬件。不过,每天这样做几

  9. ruby-on-rails - 使用 Rails 事件记录获取二级模型 - 2

    我有一个帖子属于城市的关系,城市又属于一个州,例如:classPost现在我想找到所有帖子及其所属的城市和州。我编写了以下查询来获取带有城市的帖子,但不知道如何在同一查找器中获取带有城市的相应州:@post=Post.find:all,:include=>[:city]感谢任何帮助。谢谢。 最佳答案 Post.all(:include=>{:city=>:state}) 关于ruby-on-rails-使用Rails事件记录获取二级模型,我们在StackOverflow上找到一个类似的问

  10. ruby - 在没有数据库的情况下伪造一个事件记录模型 - 2

    我觉得我错过了什么。我正在编写一个ruby​​gem,它允许与事件记录进行交互,作为其主要功能的附加功能。在为其编写测试用例时,我需要能够指定虚拟事件记录模型来测试此功能。如果我可以获得一个事件记录模型的实例,它不需要与数据库的任何连接,可以有关系,所有这些东西,但不需要我在数据库中设置表,那就太棒了。我对测试还很陌生,在Rails测试之外我也很陌生,但似乎我应该能够相当轻松地完成类似的事情,但我什么也没找到。谁能告诉我我错过了什么?我看过工厂、制造商、固定装置,所有这些似乎都想达到目标。人们如何在您只需要AR对象进行测试的地方测试gem? 最佳答案

随机推荐