C#学习 - 事件 续

2023-12-05

事件声明

完整声明

using System;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();
            Waiter waiter = new Waiter();
            customer.Order += waiter.Action;
            customer.Action();//这里也能直接写MyOrder
            customer.PayBill();
        }
    }
    //class前需加上public,否则访问级别低于OrderEventHandler会报错
    public class OrderEventArgs : EventArgs//继承于基类:EventArgs
    {   //传递事件信息,名字要以事件名+EventArgs
        public string DishName { get; set; }//菜名
        public string Size {  get; set; }//份量
    }
    public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);//第一个参数:点菜人;第二个参数:点了什么/点了多少
    //委托是为了声明某个事件而准备的,需要用EventHandler作为后缀
    public class Customer//事件拥有者:顾客(类)
    {   //class前需加上public,否则访问级别低于OrderEventHandler会报错
        private OrderEventHandler orderEventHandler;//委托字段,用来引用事件处理器
        public event OrderEventHandler Order//声明事件
        {
            add//事件处理器的添加器
            {
                this.orderEventHandler += value;//value == 传进来的EventHandler
            }
            remove//事件处理器的移除器
            {
                this.orderEventHandler -= value;
            }
        }
        public double Bill { get; set; }//账单(属性)
        public void PayBill()
        {
            Console.WriteLine("Total: ${0}", this.Bill);
        }
        public void MyOrder()//顾客点菜,触发事件
        {
            if(this.orderEventHandler != null)
            {
                OrderEventArgs e = new OrderEventArgs();
                Console.WriteLine("You can order dish.");
                e.DishName = Console.ReadLine();//选择菜名
                Console.WriteLine("Enter the portion size of the dish.");
                e.Size = Console.ReadLine();//选择份量
                this.orderEventHandler.Invoke(this, e);
            }
        }
        public void Action()//触发MyOrder
        {
            Console.ReadLine();
            this.MyOrder();
        }
    }
    public class Waiter//事件的响应者:服务员(类)
    {
        public void Action(Customer customer, OrderEventArgs e)
        {
            Console.WriteLine("Serving your {0}",e.DishName);
            double price = 5;//菜的价格
            switch(e.Size)
            {
                case "1":
                    price = price * 1;
                    break;
                case "2":
                    price = price * 2; 
                    break;
                case "3":
                    price = price * 4;
                    break;
                default:
                    break;
            }
            customer.Bill += price;
        }
    }
}

简略声明

字段式声明(field - like)

private OrderEventHandler orderEventHandler;
public event OrderEventHandler Order
{
    add
    {
        this.orderEventHandler += value;
    }
    remove
    {
        this.orderEventHandler -= value;
    }
}
//将上段代码删除,修改为
public event OrderEventHandler Order;
//同时,将下段代码中的orderEventHandler修改为事件名Order
public void MyOrder()
{
    if(this.orderEventHandler/*此处*/ != null)
    {
        OrderEventArgs e = new OrderEventArgs();
        Console.WriteLine("You can order dish.");
        e.DishName = Console.ReadLine();
        Console.WriteLine("Enter the portion size of the dish.");
        e.Size = Console.ReadLine();
        this.orderEventHandler/*此处*/.Invoke(this, e);
    }
}

简略声明中的语法与完整声明中有不一致处。在完整声明中,不能用Order(事件名)替代orderEventHandler(事件处理器),事件名只能处于 += 或 -=操作符左边;而简略声明中没有声明委托字段orderEventHandler,只能用Order替代(在简略声明中,编译器暗地里声明了一个名为Order的字段)

事件的必要性

简略声明事件与直接使用委托类似(只在声明时多了一个event),但事件能让程序逻辑以及对象之间的关系更加有好,让程序更加安全

//简略声明中删去event
static void Main(string[] args)
{
    Customer customer = new Customer();
    Waiter waiter = new Waiter();
    customer.Order += waiter.Action;
    customer.Action();

    OrderEventArgs e1 = new OrderEventArgs();
    e1.DishName = "Fried rice with egg";
    e1.Size = "1";
    OrderEventArgs e2 = new OrderEventArgs();
    e2.DishName = "Coke";
    e2.Size = "1";

    Customer c = new Customer();
    c.Order += waiter.Action;
    c.Order.Invoke(customer, e1);//事件不能用“.”操作符,而委托可以
    c.Order.Invoke(customer, e2);
    
    customer.PayBill();//customer被迫付了一份蛋炒饭和一份可乐的钱
}

事件本质

事件本质是委托字段的一个包装器

  • 包装器对委托字段的访问起限制作用
  • 封装(encapsulation)的一个重要功能就是隐藏
  • 事件对外界隐藏了委托实例的大部分功能,只暴露了添加&移除事件处理器的功能

事件类委托命名规则

声明事件A的委托,命名为AEventHandler(事件名 + EventHandler)。有个通用委托: public delegate void EventHandler(object sender, EventArgs e);
AEventHandler委托参数一般有两个

  • object类型:参数名为sender,事件拥有者
  • EventArgs类的派生类:类名一般为AEventArgs,参数名为e,事件参数
  • 委托的参数列表可以看作是事件发生后发送给事件响应者的“事件消息”
    触发A事件的方法一般命名为OnA,访问级别为protected而非public
    之前的代码中没有使用OnOrder,若要遵守规则,则需修改:
public void MyOrder()
{
		if(this.orderEventHandler != null)
	{
		OrderEventArgs e = new OrderEventArgs();
		Console.WriteLine("You can order dish.");
		e.DishName = Console.ReadLine();//选择菜名
		Console.WriteLine("Enter the portion size of the dish.");
		e.Size = Console.ReadLine();//选择份量
		this.orderEventHandler.Invoke(this, e);
	}
}
//将事件声明中的上段代码修改为下面的
public void MyOrder()
{
	Console.WriteLine("You can order dish.");
	string dish = Console.ReadLine();
	Console.WriteLine("Enter the portion size of the dish.");
	string size = Console.ReadLine();
    this.OnOrder(dish, size);
}
protected void OnOrder(string dish, string size)
{
    if(this.orderEventHandler != null)
    {
        OrderEventArgs e = new OrderEventArgs();
        e.DishName = dish;
        e.Size = size;
        this.orderEventHandler.Invoke(this, e);//事件拥有者触发事件,传this(this就是事件拥有者)
    }
}
//简略声明就把orderEventHandler改为Order

事件命名

带有时态的动词或动词短语
命名时要使用对应的时态

事件与委托的关系

事件不是以特殊方式声明的委托字段或实例:

  • 事件进行简略声明时很像委托字段
  • 订阅事件时 += 操作符后面可以是一个委托实例,与委托实例的赋值方法语法相同,让事件看起来像个字段 customer.Order += new EventHandler(waiter.Action);

委托类型来声明字段:

  • 在事件拥有者(source)来看,是为了表明能向外传递哪些信息
  • 在事件响应者(subscriber)来看,是为了约束能够使用什么样签名的方法来处理事件
  • 委托类型的实例将用于存储(引用)事件处理器

事件与属性:

  • 属性不是字段,很多时候属性是字段的包装器,用来保护字段不被滥用
  • 时间不是委托字段,是委托字段的包装器,用来保护委托字段不被滥用
  • 包装器不能被另一个包装器包装
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C#学习 - 事件 续 的相关文章

  • 如何以编程方式将访问键(快捷方式)添加到 WPF ContextMenu?

    我已经有以下内容 var myContextMenu new System Windows Controls ContextMenu var exitItem new MenuItem exitItem Header E xit exitI
  • 如何在C++中生成非常大的随机数

    我想使用 C 生成 0 2 64 范围内的非常大的随机数 我已经使用了 rand 函数 但它没有生成非常大的数字 有人可以帮忙吗 使用c 11 使用标准c 11的随机库 http en cppreference com w cpp nume
  • 无法使用c#更改视频捕获分辨率

    我正在尝试使用 C 中的 DirectShowNet 更改默认网络摄像头分辨率 据我所知 我需要通过调用 windows win32 api dll 中内置的 VideoInfoHeader 类来更改它以进行 avi 捕获 我有来自 Dir
  • 如何在 ASP.NET MVC 中将 XML 文件发送到客户端

    在 ASP NET MVC 中 我有一个数据库表 我想在某个视图页面上有一个按钮 如果某个用户单击该按钮 我的应用程序将生成包含数据库中所有行的 XML 文件 然后 应将包含 XML 的文件发送到客户端 以便用户看到下载弹出窗口 同样 我希
  • 信号与信号2

    我的应用程序可能会受益于使用 boost 的信号库之一而不是本土解决方案 该应用程序是多线程的 但执行信号处理的部分是单线程的 如果多线程不是问题 是否有任何理由更喜欢 Boost Signals2 而不是 Boost Signal Boo
  • 头文件中实现的函数的静态与内联

    我想到的方式inline在 C 中用于链接 作用域 我把它放在同一个篮子里extern and static对于全局对象 通常 对于在头文件中实现的函数 我的首选解决方案是将其设为静态 In Foo h static void foo Do
  • C# 中 PKCS11Interop 库的线程安全使用 [已关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我正在使用 PKCS11Interop 在 HSM 内执行密钥管理操作 我使用的 HSM 是 Thales PCI Express 下面是
  • 预编译头和 Visual Studio

    有没有办法设置 Visual Studio 解决方案参数 以便它只创建预编译头而不构建整个解决方案 具体来说 它是一个巨大的 C 解决方案 本身有许多项目 谢谢 仅选择 pch 创建者源文件 通常是 stdafx cpp 然后编译该文件 C
  • 检测反射 DLL 注入

    在过去的几年中 恶意软件 以及一些渗透测试工具 如 Metasploit 的 meterpreter 负载 已经开始使用反射 DLL 注入 PDF http www harmonysecurity com files HS P005 Ref
  • 模板与非模板类,跨编译器的不同行为

    我在一些应用程序中使用编译时计数器 它确实很有用 昨天我想用 gcc 编译一个程序 我之前使用的是 msvc 并且计数器的行为在模板类中发生了变化 它在模板类中不再工作 过于简化的代码 Maximum value the counter c
  • ASP.NET MVC 动作过滤器

    有谁知道即使在 CATCH 块中 ActionFilterAttribute 类的 OnResultExecuted 方法是否也会执行 ie CookiesActions public ActionResult Login Usuarios
  • linq where 子句和 count 导致 null 异常

    除非 p School SchoolName 结果为 null 否则下面的代码将起作用 在这种情况下 它会导致 NullReferenceException if ExistingUsers Where p gt p StudentID i
  • 使用 dateTimePicker 在 DataGridView 中编辑日期

    我有一个DateTime我的 WinForms 中的专栏DataGridView 目前只能通过手动输入日期来编辑该字段 例如 2010 09 02 需要什么才能拥有一个DateTimePicker 或同等 用作编辑器 DataGridVie
  • 意外的 const 引用行为

    include
  • 如何释放字符串未使用的容量

    我正在程序中处理很多字符串 这些字符串数据在读入我的程序后的整个生命周期内都不会改变 但由于 C 字符串保留了容量 因此浪费了大量肯定不会被使用的空间 我尝试释放这些空间 但没有成功 以下是我尝试过的简单代码 string temp 123
  • 展开 std::reference_wrapper 的成本

    Given include
  • 为什么 C++ 标准没有将 sizeof(bool) 定义为 1?

    Size of char signed char and unsigned char由 C 标准本身定义为 1 个字节 我想知道为什么它没有定义sizeof bool also C 03 标准 5 3 3 1 说 sizeof char s
  • 检查一个数是否是完全平方数?

    我认为以下代码存在精度问题 bool isPerfectSquare long long n long long squareRootN long long sqrt n 0 5 return squareRootN squareRootN
  • 如何通过API退出Win32应用程序?

    我有一个使用 Win32 API 编写的 C Win32 应用程序 我希望强制它在其中一个函数中退出 有没有类似的东西Exit or Destroy or Abort 类似的东西会终止它吗 哎呀呀呀呀呀呀 不要做任何这些事情 exit 和
  • 创建进程默认浏览器

    我目前正在使用 ShellExecute 打开 在用户浏览器中打开 URL 但在 Win7 和 Vista 中遇到了一些麻烦 因为该程序作为服务运行提升 我想获取线程 id 因此 ShellExecute 无法获取线程 id 因此我开始使用

随机推荐