C# 中的事件

在 C# 中,事件是一种成员,用于将特定的事件通知发送给订阅者。事件通常用于实现观察者模式,它允许一个对象将状态的变化通知其他对象,而不需要知道这些对象的细节。

事件的基本概念

事件基本上是一个用户操作,如按键、点击、鼠标移动等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。。。。

简单粗暴的理解就是 我在一个A地触发了这个事件,需要在B地做出对应的回应

事件的几大要素

一、事件的声明要素

1、委托类型(Delegate Type):定义事件处理方法的签名;必须与事件处理方法和参数和返回类型匹配;可以使用预定义委托(EventHandler<T>)或自定义委托,本次使用的是预定义委托

//自定义委托
public delegate void MainWindowEvntSelectedHandler(object sender,MainWindowEvntArgs e)

//预定义委托
public static event EventHandler<MainWindowEvntArgs> MainWindowEvntSelected;

2、事件关键字(event):声明成员为事件,只允许+=,-=操作

3、事件发布者:声明和触发事件的类;控制事件何时被触发;包含事件触发方法(On开头)

        public static class MainWindowEventBus
        {
            //定义事件
            public static event EventHandler<MainWindowEvntArgs> MainWindowEvntSelected;
            //触发事件的方法
            public static void OnMainWindowEvntSelected(string message1, string message2)
            {
                MainWindowEvntSelected?.Invoke(null, new MainWindowEvntArgs
                {
                    labelMessage = message1,
                    buttonMessage = message2
                });
            }
        }

二、事件的订阅和取消

1、事件订阅(Subscription)

  • 使用+=操作符添加事件处理方法
  • 处理方法必须与事件委托类型兼容
  • 可以是实例方法、静态方法、匿名方法或 Lambda 表达式
        public WindowB()
        {
            InitializeComponent();
            MainWindowEventBus.MainWindowEvntSelected += OnMainWindowEvntSelected;//订阅事件
            this.Unloaded += WindowB_Unloaded;
        }

2、事件取消订阅(Unsubscription)

  • 使用-=操作符移除事件处理方法
  • 必须使用与订阅时相同的方法引用
  • 防止内存泄漏和意外调用
        private void WindowB_Unloaded(object sender, RoutedEventArgs e)
        {
            MainWindowEventBus.MainWindowEvntSelected -= OnMainWindowEvntSelected;//取消订阅事件
        }

三、事件要素的关系图  

发布者(Publisher)
├── 声明事件(event)
│   └── 基于委托类型(Delegate)
├── 触发事件(OnEvent())
│   └── 传递 sender 和 EventArgs
└── 维护订阅者列表

订阅者(Subscriber)
├── 订阅事件(+=)
│   └── 提供兼容的处理方法
└── 取消订阅(-=)

事件参数(EventArgs)
├── 继承自 System.EventArgs
└── 封装事件相关数据

四、实际例子

比如我在界面A触发事件,希望B页面发生变化,这在生产环境中是比较常用的,比如,两个页面都有配方表格,生产人员会希望在A页面加载完配方B页面的配方会随着变化;或者在A页面按下操作按钮,隐藏B页面的部分内容等等;

1、在主页面编写事件参数和事件方法

xaml代码

<Window x:Class="Demo_Event.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Demo_Event"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Label Content="这是A页面" Width="150" Height="50" FontSize="20" VerticalAlignment="Top" HorizontalAlignment="Left"/>
        <Label x:Name="ceshi_label" Width="150" Height="50" FontSize="20"/>
        <Button x:Name="btn1" Height="50" VerticalAlignment="Bottom" Content="A事件" FontSize="15" Margin="333,0,329,71" Click="Button_Click"/>
    </Grid>
</Window>

mainWindow.cs代码

namespace Demo_Event
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private WindowB _windowB;
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            ceshi_label.Content = "在A触发事件";

            if (_windowB == null)
            {
                _windowB = new WindowB();
                _windowB.Closed += (s, ev) => _windowB = null;
                _windowB.WindowStartupLocation= WindowStartupLocation.CenterScreen;
                _windowB.Show();            
            }
            MainWindowEventBus.OnMainWindowEvntSelected(ceshi_label.Content.ToString(), btn1.Content.ToString());
        }
        /// <summary>
        /// 定义事件参数,传递信息
        /// </summary>
        public class MainWindowEvntArgs:EventArgs
        {
            public string labelMessage {  get; set; }//传递要给出的信息
            public string buttonMessage { get; set; }
        }
        public static class MainWindowEventBus
        {
            //定义事件
            public static event EventHandler<MainWindowEvntArgs> MainWindowEvntSelected;
            //触发事件的方法
            public static void OnMainWindowEvntSelected(string message1, string message2)
            {
                MainWindowEvntSelected?.Invoke(null, new MainWindowEvntArgs
                {
                    labelMessage = message1,
                    buttonMessage = message2
                });
            }
        }
    }
}

2、在其他页面订阅该事件

B页面的Xaml代码

<Window x:Class="Demo_Event.WindowB"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Demo_Event"
        mc:Ignorable="d"
        Title="WindowB" Height="450" Width="800">
    <Grid>
        <Label Content="这是B页面" Width="150" Height="50" FontSize="20" VerticalAlignment="Top" HorizontalAlignment="Left"/>
        <Label Content="文本信息" Width="100" Height="50" FontSize="20" Margin="112,119,588,266"/>
        <Label Content="按钮信息" Width="100" Height="50" FontSize="20" Margin="112,182,588,203"/>
        <TextBox x:Name="txt_ceshi1" FontSize="15" Width="200" Height="50" Margin="220,119,380,266" />
        <TextBox x:Name="txt_ceshi2" FontSize="15" Width="200" Height="50" Margin="220,182,380,203" />
    </Grid>
</Window>

B页面的CS代码 

namespace Demo_Event
{
    /// <summary>
    /// WindowB.xaml 的交互逻辑
    /// </summary>
    public partial class WindowB : Window
    {
        public WindowB()
        {
            InitializeComponent();
            MainWindowEventBus.MainWindowEvntSelected += OnMainWindowEvntSelected;//订阅事件
            this.Unloaded += WindowB_Unloaded;
        }

        private void WindowB_Unloaded(object sender, RoutedEventArgs e)
        {
            MainWindowEventBus.MainWindowEvntSelected -= OnMainWindowEvntSelected;//取消订阅事件
        }

        //事件处理方法
        private void OnMainWindowEvntSelected(object sender, MainWindowEvntArgs e)
        {
            txt_ceshi1.Text=e.labelMessage;
            txt_ceshi2.Text=e.buttonMessage;
        }
    }
}

五、运行效果

 

答案是他会接着触发事件回到步骤2

六、特殊情况

需要注意的是如果是在单例模式(Instance)下,上述的写法只会在加载的时候,第一次触发事件有效,后续就无效了

问题的根源是,单例模式下对象一旦被创建就无法被摧毁,只有程序退出才会被摧毁

第一次加载:订阅事件 ✅

第一次卸载:移除订阅 ✅

第二次加载:尝试再次订阅,但单例实例已存在,Loaded事件不会触发订阅逻辑 ❌

后续事件触发:由于订阅已被移除且未重新订阅,事件处理方法不会执行 ❌

导致不会继续订阅​​​​​​​

    单例模式的核心特性

    • 唯一性:整个应用程序中只有一个实例
    • 全局访问:通过静态方法Instance()全局访问
    • 延迟初始化:实例在第一次使用时创建

    解决方案:添加一个_isEventSubscribed 状态来判断他是否在订阅 ;增加了一个确保事件被订阅的方法EnsureEventSubscribed,这样就能始终记录事件的订阅状态,从而一直触发

        public partial class WindowB : Window
        {
            private static WindowB _instance;
            private static readonly object _lock = new object();
            private bool _isEventSubscribed = false;
    
            public static WindowB Instance()
            {
                if (_instance == null)
                {
                    lock (_lock)
                    {
                        if (_instance == null)
                        {
                            _instance = new WindowB();
                        }
                    }
                }
                _instance.EnsureEventSubscribed();
                return _instance;
            }
            public WindowB()
            {
                InitializeComponent();
                MainWindowEventBus.MainWindowEvntSelected += OnMainWindowEvntSelected;//订阅事件
                this.Unloaded += WindowB_Unloaded;
            }
            private void EnsureEventSubscribed()
            {
                if (!_isEventSubscribed)
                {
                    MainWindowEventBus.MainWindowEvntSelected += OnMainWindowEvntSelected;
                    _isEventSubscribed = true;
                }
            }
            private void WindowB_Unloaded(object sender, RoutedEventArgs e)
            {
                if (_isEventSubscribed)
                {
                    MainWindowEventBus.MainWindowEvntSelected -= OnMainWindowEvntSelected;//取消订阅事件
                    _isEventSubscribed= false;
                }
                
            }
    
            //事件处理方法
            private void OnMainWindowEvntSelected(object sender, MainWindowEvntArgs e)
            {
                txt_ceshi1.Text = e.labelMessage;
                txt_ceshi2.Text = e.buttonMessage;
            }
        }

    更多推荐