C#中的事件(委托的发布和订阅、事件的发布和订阅、EventHandler类、Windows事件)实例详解,观察者(Observer)模式也称发布-订阅(Publish-Subscribe)模式

观察者(Observer)模式也称发布-订阅(Publish-Subscribe)模式,定义了对象间一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 

目录

一、委托的发布和订阅

1.订阅操作符号“+="和取消订阅操作符号“-=”

2.示例源码

二、事件的发布和订阅

三、EventHandler类

四、Windows事件

        C#中的事件是指某个类的对象在运行过程中遇到的一些特定事情,而这些特定的事情有必要通知给这个对象的使用者。当发生与某个对象相关的事件时,类会使用事件将这一对象通知给用户,这种通知即称为“引发事件”。引发事件的对象称为事件的源或发送者。

一、委托的发布和订阅
        由于委托能够引用方法,而且能够链接和删除其他委托对象,因而就能够通过委托来实现事件的“发布和订阅”。

  通过委托来实现事件处理的过程,通常需要以下4个步骤:
• 定义委托类型,并在发布者类中定义一个该类型的公有成员。
• 在订阅者类中定义委托处理方法。
• 订阅者对象将其事件处理方法链接到发布者对象的委托成员(一个委托类型的引用)上。
• 发布者对象在特定的情况下“激发”委托操作,从而自动调用订阅者对象的委托处理方法。
1.订阅操作符号“+="和取消订阅操作符号“-=”
         “+=”在这里不是逻辑运算符,而是用于指定响应事件时要调用的方法。这类方法称为事件处理程序,叫 注册/订阅事件,用在操作类名后。

//订阅符号+=
public static void SubscribeToRing(SchoolRing schoolRing)  
{
    schoolRing.OnBellSound += SchoolJow;
}
        与之相反功能的“-=”就是取消订阅、退订操作符。

//取消订阅操作符“-=”
public static void CancelSubscribe(SchoolRing schoolRing)  
{
    schoolRing.OnBellSound -= SchoolJow;
}
2.示例源码
// 委托的发布和订阅事件
 
namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            SchoolRing sr = new();                          //创建学校铃声类的对象
            Students.SubscribeToRing(sr);                   //订阅铃声
            Console.Write("请输入打铃参数(1:表示打上课铃;2:表示打下课铃):");
            sr.Jow(Convert.ToInt32(Console.ReadLine()));    //打铃动作
            Console.ReadLine();
        }
    }
 
    public delegate void RingEvent(int ringKind);           //声明一个委托类型
 
    /// <summary>
    /// 定义铃声类SchoolRing
    /// 类中发布一个委托,定义函数Jow
    /// </summary>
    public class SchoolRing
    {
        public RingEvent? OnBellSound;            //委托发布,就好像定义一个实例对象
        public void Jow(int ringKind)             //定义一个公有成员Jow(),打铃
        {
            if (ringKind == 1 || ringKind == 2)
            {
                Console.Write(ringKind == 1 ? "上课铃声响了," : "下课铃声响了,");
                if (OnBellSound != null)         //不等于空,说明它已经订阅了具体的方法(即它已经引用了具体的方法)
                {
                    OnBellSound!(ringKind);      //回调OnBellSound委托所订阅(或引用)的具体方法
                }
            }
            else
            {
                Console.WriteLine("这个铃声参数不正确!");
            }
        }
    }
 
    /// <summary>
    /// 定义学生类Students
    /// 类中定义三个函数
    /// </summary>
    public class Students
    {
        public static void SubscribeToRing(SchoolRing schoolRing)  //学生们订阅铃声这个委托事件
        {
            schoolRing.OnBellSound += SchoolJow;
        }
 
        public static void SchoolJow(int ringKind)
        {
            if (ringKind == 2)             //打了下课铃
            {
                Console.WriteLine("同学们开始课间休息!");
            }
            else if (ringKind == 1)        //打了上课铃
            {
                Console.WriteLine("同学们开始认真学习!");
            }
        }
 
        public static void CancelSubscribe(SchoolRing schoolRing)  //取消订阅铃声动作
        {
            schoolRing.OnBellSound -= SchoolJow;
        }
    }
}
二、事件的发布和订阅
        事件是一种特殊的类型,发布者在发布一个事件之后,订阅者对它只能进行自身的订阅或取消,而不能干涉其他订阅者。
        事件是类的一种特殊成员:即使是公有事件,除了其所属类型之外,其他类型只能对其进行订阅或取消,别的任何操作都是不允许的,因此事件具有特殊的封装性。和一般委托成员不同,某个类型的事件只能由自身触发。
        使用事件的目的是:解决安全隐患和不能干涉其他订阅者。事件的使用方法:C#提供了专门的事件处理机制,以保证事件订阅的可靠性,其做法是在发布委托的定义中加上event关键字,其他代码不变。

//事件的使用方法
public event RingEvent OnBellSound;    //事件发布
//不安全的事件订阅,当不使用event关键字时,系统会会忽视威胁的存在
//当使用event关键字修饰时,系统会报错
schoolRing.OnBellSound = SchoolJow;   //系统会报错的,应使用+=
schoolRing.OnBellSound = null;        //系统会报错的,禁止指向null
schoolRing.OnBellSound2 = SchoolJow;  //系统会报错的,事件只能由自身触发
三、EventHandler类
        在事件发布和订阅的过程中,定义事件的类型(即委托类型)是一件重复性的工作,为此,.NET类库中定义了一个EventHandler委托类型,并建议尽量使用该类型作为事件的委托类型。该委托类型的定义为:

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

        object类型的参数sender表示引发事件的对象,由于事件成员只能由类型本身(即事件的发布者)触发,因此在触发时传递给该参数的值通常为this。例如,可将SchoolRing类的OnBellSound事件定义为EventHandler委托类型,那么触发该事件的代码就是“OnBellSound(this,null);”。

        EventHandler委托的第二个参数e表示事件中包含的数据。如果发布者还要向订阅者传递额外的事件数据,那么就需要定义EventArgs类型的派生类。

// EventHandler类
namespace _09_1
{
    class Program
    {
        /// <summary>
        /// 操作流程:创建发布者实例→订阅该实例→发布者开始发布
        /// </summary>
        static void Main(string[] args)
        {
            SchoolRing sr = new();                                     //创建学校铃声类的对象
            Students.SubscribeToRing(sr);                          //订阅铃声
            Console.Write("请输入打铃参数(1:表示打上课铃;2:表示打下课铃):");
            sr.Jow(Convert.ToInt32(Console.ReadLine()));    //发布者触发打铃动作,事件只能由发布者触发
            Console.ReadLine();
        }
    }
 
    public delegate void RingEvent(int ringKind);           //声明一个委托类型
 
    /// <summary>
    /// 发布者
    /// 校铃种类及对应的处理方法
    /// 定义铃声类SchoolRing,类中发布一个委托,定义函数Jow方法
    /// </summary>
    public class SchoolRing
    {
        public event EventHandler? OnBellSound;   //委托发布,就好像定义一个实例对象
        public void Jow(int ringKind)                      //定义一个公有成员Jow(),打铃方法
        {
            if (ringKind == 1 || ringKind == 2)
            {
                Console.Write(ringKind == 1 ? "上课铃声响了," : "下课铃声响了,");
                if (OnBellSound != null)                    //不等于空,说明它已经订阅了具体的方法(即它已经引用了具体的方法)
                {                                                     //为了安全,事件成员只能由类型本身触发(this),
                    OnBellSound!(this, new Students.RingEventArgs(ringKind));
                }
            }
            else
            {
                Console.WriteLine("这个铃声参数不正确!");
            }
        }
    }
 
    /// <summary>
    /// 订阅者
    /// 定义学生类Students
    /// 类中定义三个函数:订阅、订阅方法、取消订阅
    /// </summary>
    public class Students
    {
        /// <summary>
        /// 订阅
        /// </summary>
        public static void SubscribeToRing(SchoolRing schoolRing)  //学生们订阅铃声这个委托事件
        {
            schoolRing.OnBellSound += SchoolJow;
        }
 
        /// <summary>
        /// EventHandler委托的第二个参数e表示事件中包含的数据。
        /// </summary>
        /// <param name="sender">
        /// 事件的订阅者可以通过sender参数来了解是哪个对象触发的事件(这里当然是事件的发布者),
        /// 不过在访问对象时通常要进行强制类型转换
        /// </param>
        /// <param name="e"></param>
        public static void SchoolJow(object? sender, EventArgs e)
        {
            if (((RingEventArgs)e).RingKind == 2)             //下课铃,e强制转化内RingEventArgs类型
            {
                Console.WriteLine("同学们开始课间休息!");
            }
            else if (((RingEventArgs)e).RingKind == 1)     //上课铃,e强制转化内RingEventArgs类型
            {
                Console.WriteLine("同学们开始认真学习!");
            }
        }
 
        /// <summary>
        /// 取消订阅
        /// </summary>
        /// <param name="schoolRing"></param>
        public static void CancelSubscribe(SchoolRing schoolRing)
        {
            schoolRing.OnBellSound -= SchoolJow;
        }
 
        /// <summary>
        /// EventArgs类型的派生类
        /// 如果发布者还要向订阅者传递额外的事件数据,那么就需要定义EventArgs类型的派生类。
        /// 例如,由于需要把打铃参数(1或2)传入事件中,则可以定义如下的RingEventArgs类:
        /// </summary>
        /// <param name="ringKind">
        /// 铃声参数
        /// </param>
        public class RingEventArgs(int ringKind) : EventArgs
        {
            //描述铃声种类的字段
            private readonly int ringKind = ringKind;
            //获取打铃参数
            public int RingKind         
            {
                get { return ringKind; }
            }
        }
    }
}
四、Windows事件
        事件在Windows这样的图形界面程序中有着极其广泛的应用,事件响应是程序与用户交互的基础。用户的绝大多数操作,都可以触发相关的控件事件。关于此类事件,详见有关Windows窗体应用的文章。

/************************************

我们用一个简单的例子,来说明一下这种消息传递的机制。

  有一家三口,妈妈负责做饭,爸爸和孩子负责吃。。。将这三个人,想象成三个类。

  妈妈有一个方法,叫做“做饭”。有一个事件,叫做“开饭”。做完饭后,调用开发事件,发布开饭消息。

  爸爸和孩子分别有一个方法,叫做“吃饭”。

  将爸爸和孩子的“吃饭”方法,注册到妈妈的“开饭”事件。也就是,订阅妈妈的开饭消息。让妈妈做完饭开饭时,发布吃饭消息时,告诉爸爸和孩子一声。

  这种机制就是C#中的,订阅发布。下面我们用代码实现:


 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 
 6 namespace EventSimpleDemo
 7 {
 8     class Program
 9     {
10         static void Main(string[] args)
11         {
12             Mom mom = new Mom();
13             Dad dad = new Dad();
14             Son son = new Son();
15             mom.Eat += new Action(dad.Eat);// 订阅
16             //mom.Eat += new Action(son.Eat);
17 
18             mom.Cooking();
19             Console.ReadKey();
20         }
21     }
22 
23     public class Mom
24     {
25         //public delegate void delegateAction();
26         public event Action Eat;// Action 可以改成委托方法:delegateAction
27 
28         public void Cooking()
29         {
30             Console.WriteLine("妈妈:饭好了!");
31             if (Eat != null)
32             {
33                 Eat();
34             }
35         } 
36     }
37 
38     public class Dad
39     {
40         public void Eat()
41         {
42             Console.WriteLine("爸爸:马上来!");
43         }
44     }
45 
46     public class Son
47     {
48         public void Eat()
49         {
50             Console.WriteLine("儿子:等会再吃!");
51         }
52     }
53 
54 }

 

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值