【C#语言入门】18. 事件详解(下)

发布于:2024-03-12 ⋅ 阅读:(36) ⋅ 点赞:(0)

【C#语言入门】18. 事件详解(下)

三、事件的声明

  • 事件的声明
    • 完整声明
    • 简略声明(字段式声明。field-like)

完整声明

class EventExample1
{
    static void Main(string[] args)
    {
        Customer customer = new Customer();
        Waiter waiter = new Waiter();
        customer.Order += waiter.Action;
        customer.Action();
        customer.PayTheBill();
    }

}

public class OrderEventArgs
{
    public string DishName { get; set; }
    public string Size {  get; set; }
}

public delegate void OrderEventHandler(Customer cumtomer, OrderEventArgs e);

public class Customer
{
    private OrderEventHandler orderEventHandler;

    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 down.");
    }

    public void Think()
    {
        for (int i = 0;i < 5;i++)
        {
            Console.WriteLine("Emm...Let me think..");
        }

        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 *= 0.5;
                break;
            case "large":
                price *= 1.5;
                break;
            default:
                break;
        }

        customer.Bill += price;
    }
}

简易声明,会有误解,事件名取代了字段名

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 down.");
    }

    public void Think()
    {
        for (int i = 0;i < 5;i++)
        {
            Console.WriteLine("Emm...Let me think..");
        }

        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();
    }
}
  • 有了委托字段/属性,为什么还需要事件
    • 为了程序的逻辑更加“有道理”、更加安全,谨防“借刀杀人”,事件的触发必须由事件自己来做。
class EventExample1
{
    static void Main(string[] args)
    {
        Console.ReadLine();
        Customer customer = new Customer();
        Waiter waiter = new Waiter();
        customer.Order += waiter.Action;
        //customer.Action();
        
        OrderEventArgs e1 = new OrderEventArgs();
        e1.DishName = "BigggDinner";
        e1.Size = "large";

        OrderEventArgs e2 = new OrderEventArgs();
        e2.DishName = "Beer";
        e2.Size = "large";

        Customer badGuy = new Customer();
        badGuy.Order += waiter.Action;
        badGuy.Order.Invoke(customer,e1);
        badGuy.Order.Invoke(customer,e2);

        customer.PayTheBill();

    }

}

public class OrderEventArgs
{
    public string DishName { get; set; }
    public string Size {  get; set; }
}

public delegate void OrderEventHandler(Customer cumtomer, OrderEventArgs e);

public class Customer
{
    public OrderEventHandler Order;//没有了Event,变成了字段,谁都可以访问它
    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 down.");
    }

    public void Think()
    {
        for (int i = 0;i < 5;i++)
        {
            Console.WriteLine("Emm...Let me think..");
        }

        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 *= 0.5;
                break;
            case "large":
                price *= 1.5;
                break;
            default:
                break;
        }

        customer.Bill += price;
    }
}
  • 所以事件的本质是委托字段的一个包装器
    • 这个包装器对委托字段的访问起到限制作用,相当于一个蒙版
    • 封装(encapsulation)的一个重要功能就是隐藏
    • 事件对外界隐藏了委托实例的大部分功能,仅暴露添加/移除事件处理器的功能
  • 用于声明事件的委托类型的命名约定
    • 用于声明Foo事件的委托,一般命名为FooEventHandler(除非是一个非常通用的事件约束)
    • FooEventHandler委托的参数一般有两个
      • 第一个是object类型,名字为sender,实际上就是事件的拥有者,事件的source
      • 第二个是EventArgs类的派生类,类名一般为FooEventArgs,参数名为e。也就是前面讲过的事件参数
      • 虽然没有官方的说法,但我们可以把委托的参数列表看做是事件发生后送给事件响应者的“事件消息”
    • 触发Foo事件的方法一般命名为OnFoo,即”因何引发“、”事出有因“
      • 访问级别为protected,不能为public,不然又可以“借刀杀人”
  • 事件的命名约定
    • 带有时态的动词或者动词短语
    • 事件拥有者“正在做”什么事情,用进行时;事件拥有者“做完了”什么事情,用完成时
public void Think()
{
    for (int i = 0;i < 5;i++)
    {
        Console.WriteLine("Emm...Let me think..");
    }

    this.OnOrder("Kongpao Chicken", "large");

    
}

protected void OnOrder(string dishName, string size)//protected保护了这个方法,不让外界使用
{
    if (this.Order != null)
    {
        OrderEventArgs e = new OrderEventArgs();
        e.DishName = dishName;
        e.Size = size;
        this.Order.Invoke(this, e);
    }
}
//优化,确保一个方法只做一件事情

四、事件与委托的关系

  • 事件真的是“以特殊方式声明的委托字段/实例”吗?
    • 错误的。只是声明的时候“看起来像”(对比委托字段与事件的简化声明,field-like)
    • 事件声明的时候使用了委托类型,简化声明造成时间看上去像一个委托的字段(实例),而event关键字则更像是一个修饰符——这就是错觉的来源之一
    • 订阅事件的时候 += 操作符后面可以是一个委托实例,这与委托实例的赋值方法语法相同,这也让事件看起来像是一个委托字段——这是错觉的又一来源
    • 重申:事件的本质是加装在委托字段上的一个“蒙版”,是个起掩蔽作用的包装器。这个用于阻挡非法操作的蒙版绝不是委托字段本身
  • 为什么要使用委托类型来声明事件?
    • 站在source的角度来看,是为了表明source能对外传递哪些消息
    • 站在subscriber的角度来看,它是一种约定,是为了约束能够使用什么样签名的方法来处理(响应)事件
    • 委托类型的实例将用于存储(引用)事件处理器
  • 对比事件和属性
    • 属性不是字段——很多时候属性是字段的包装器,这个包装器用来保护字段不被滥用
    • 事件不是委托字段——它是委托字段的包装器,这个包装器用来保护委托字段不被滥用
    • 包装器永远都不可能是被包装的东西
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到