关于静态事件 static event 的二三事

很多人都知道事件,一般定义一个事件是这样的写法

public event EventHandler OneEvent;

但是如果这样定义的话就需要进行null判断了

于是很多人这样定义事件:

public event EventHandler OneEventNeverNull = (o, e) => { };

 

这样就不需要判断null了。

 

上面的事件你可以把它认为是实例事件. 我们这里要谈的是静态事件(static event).

静态事件和实例事件的区别是加了个static 关键字,可别小看这个关键字,区别大了。

 

首先静态事件的定义为:

public static event EventHandler OneStaticEvent;

 

如果想要注册事件的话,那么必须使用类。

image

 

在.net 的事件里面,绝大多数是实例事件,但是却存在极少数的静态事件。

 

关于静态事件StackOverflow 上面有篇文章讨论它

地址如下:http://stackoverflow.com/questions/7045595/how-do-static-events-compare-to-non-static-events-in-c

 

我个人认为答案都不错,选了几个,试着翻译如下:

大部分的面向对象程序都可以被认为是消息的传递。

一个方法调用就是调用者发送给被调用者的携带参数的一个消息,和一个使用return value 做为返回值的消息。

一个事件是从source到订阅者的一个消息。因此就有两个实例参与其中,一个用来发送消息,另一个接收它。

但是静态事件,没有发送的实例(只是一个类型,它可能是也可能不是一个class)。

 

使用 静态事件的时候,要谨记,当一个对象订阅了一个静态事件,事件就持有了这个对象的引用,这就意味着你必须非常小心的显式的取消订阅事件,因为静态事件保留了对象引用,所以这些对象就永远不会被回收,你很可能碰到内存泄漏。

 

这些答案都说明了静态事件的一些特性。总结下:

1:静态事件没有发送的实例对象。

2:静态事件必须取消订阅,否则对象无法释放,事实上任何事件都应该取消订阅,有始有终。

 

对于第二点没有什么好考虑的,可是第一点?

静态事件没有发送的实例?

a:这代表静态事件不需要实例就可以,它代表的是类级别的事件。

b:这代表静态事件没有实例,或者说无法获取这个类的实例,所以使用静态事件。

 

从这两个理解,我们可以知道什么时候应该使用静态事件了。

a:从面向对象的角度,如果一个对象只想获取事件的通知,而不关心是谁发送的,它只关心事件发生。

b:这个类没有实例?,什么类没有实例???,静态类,抽象类,或者是一些特殊的类。

 

好了,我不卖关子了,让我们看看framework里面的静态事件吧,代码如下:

Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();

foreach (Assembly assembly in assemblies)
{
    foreach (Type type in assembly.GetTypes())
    {
        EventInfo[] events = type.GetEvents(BindingFlags.Public | BindingFlags.Static);
        if (events.Length > 0)
        {
            Console.WriteLine(type.ToString());
        }
    }
}
结果如下:
 

 

微软的开发人员只为这些类定义了静态事件,最后一个是我自己写的,我们看下这些究竟是些什么类。

 

System.Console

静态类,无法获取类的实例,所以静态事件。


System.Threading.Tasks.TaskScheduler

抽象类,它的静态事件定义如下:

//     当出错的 System.Threading.Tasks.Task 的未观察到的异常将要触发异常升级策略时发生,默认情况下,这将终止进程。
        public static event EventHandler<UnobservedTaskExceptionEventArgs> UnobservedTaskException;

只要应用程序注册了 UnobservedTaskException 事件,任何TaskScheduler的子类在发生exception的时候,都会通知到应用程序。


System.Diagnostics.Eventing.EventProviderDataStream

没找到。。。


System.Diagnostics.Contracts.Contract

静态类,无法获取类的实例,所以静态事件。


System.Windows.Forms.Application

密封类,构造函数为私有,外界无法直接获取Application 的实例。


System.Windows.Forms.ToolStripManager

密封类,构造函数为私有,外界无法直接获取实例。


System.ComponentModel.TypeDescriptor

密封类,构造函数为私有,外界无法直接获取实例。


Microsoft.Win32.SystemEvents

密封类,构造函数为私有,外界无法直接获取实例。

 

System.Net.NetworkInformation.NetworkChange

密封类,构造函数为私有,外界无法直接获取实例。

StaticEventDemo.Test

出现的理由:在某天夜晚,我自己想当然的写下的没有经过思考的一个静态事件。

 

你知道微软使用静态事件的策略了吗?,只有在不能获取实例的情况下才选择静态事件。

 

有趣的一件事:本人在baidu下搜索:static event, 看到了一篇文章:

net 在类中的Event事件,为什么可以定义为static?而委托类型却不可以

也可以在这里http://www.2cto.com/kf/201110/108280.html看到代码。

 

我看到了下面的这段:

private void button1_Click(object sender, EventArgs e)
{
            clsEvent clsevent = new clsEvent();
            //改变属性,从而激发事件。
            clsevent.StrContent = this.textBox1.Text;

 

我很想说,在改变属性前,是不是要注册点什么啊,我想这就是他为什么定义为static event的原因吧。

 

总结:

在无法获取实例的情况下,又想获得通知,应该使用静态事件。

不管发送者是谁,只考虑接收事件。

静态事件一定要取消订阅,否则内存泄漏。

想要获取静态类的通知时,使用静态事件。

想要得到静态属性和方法的通知的时候。

<think>好的,我现在需要分析用户的问题:用户已经在`MainViewModel`中添加了静态事件`InstanceChanged`,但替换`Ins`实例后界面仍然没有更新。首先,我需要回顾之前的对话,用户的问题是关于反序列化后界面未更新,特别是`MainViewModel.Ins`被替换后,界面没有收到通知。用户现在提到即使添加了`InstanceChanged`事件,问题仍然存在,所以需要找出可能的原因。 首先,考虑事件订阅的问题。用户可能没有在界面(如窗口)中订阅`InstanceChanged`事件,或者订阅的代码没有正确执行。如果界面没有订阅该事件,即使事件被触发,界面也不会响应。 其次,事件触发的时机是否正确。当`Ins`被赋值时,`InstanceChanged`是否被正确触发?需要检查`setter`中的代码,确保在`_Instace = value`之后调用了`InstanceChanged?.Invoke()`,没有被其他条件阻断。 第三,事件处理逻辑是否正确。即使事件被触发,处理事件的方法是否正确更新了界面的`DataContext`?例如,在事件处理中是否将`DataContext`设置为新的`Ins`实例,或者是否有其他逻辑错误导致更新未生效。 另外,可能存在多线程问题。如果`Ins`的赋值操作发生在非UI线程,事件触发可能也是在非UI线程,导致界面无法更新。需要确认反序列化和赋值操作是否在UI线程执行。 还有,界面的绑定路径是否正确。如果界面绑定的是旧实例的属性,即使`DataContext`更新了,绑定路径可能指向错误的属性名,或者绑定模式设置不正确(如`OneTime`)。 此外,`NotifyPropertyBase`基类的实现是否正确。如果基类中的`RaisePropertyChanged`方法没有正确触发事件,即使属性值改变,界面也不会更新。特别是对于`Ins`替换后的新实例中的属性,是否每个属性都正确实现了`INotifyPropertyChanged`。 可能的原因总结: 1. 界面未订阅`InstanceChanged`事件,导致事件触发后无响应。 2. `InstanceChanged`事件触发时,界面的事件处理方法未正确更新`DataContext`。 3. `Ins`的赋值操作在非UI线程执行,导致事件在非UI线程触发,界面无法更新。 4. 界面的绑定路径或模式配置错误,无法响应新实例的属性变更。 5. 新实例中的属性未正确实现`INotifyPropertyChanged`,导致属性变更时无通知。 6. `InstanceChanged`事件被多次触发或未触发(例如,`_Instace`被赋值为相同实例时仍触发事件,或者条件判断阻止了事件触发)。 接下来需要针对这些可能的原因,给出具体的分析和解决方案。 </think>即使添加了 `InstanceChanged` 静态事件,界面仍未更新的核心原因可能与**事件未被正确订阅**、**事件处理逻辑未生效**或**多线程/绑定配置问题**有关。以下是具体分析及解决方案: ### 原因1:界面未订阅 `InstanceChanged` 事件 `InstanceChanged` 事件需要被界面(如窗口)显式订阅,否则事件触发时无任何响应。若代码中未在界面初始化时订阅该事件,即使 `Ins` 被替换,界面也无法感知。 #### 问题代码示例(未订阅事件) ```csharp // MainWindow.xaml.cs public MainWindow() { InitializeComponent(); DataContext = MainViewModel.Ins; // 未订阅 InstanceChanged 事件! } ``` #### 解决方案:在界面初始化时订阅事件 在窗口构造函数或加载事件中订阅 `InstanceChanged` 事件,并在事件处理中更新 `DataContext`: ```csharp public MainWindow() { InitializeComponent(); DataContext = MainViewModel.Ins; MainViewModel.InstanceChanged += OnMainViewModelInstanceChanged; // 订阅事件 } private void OnMainViewModelInstanceChanged() { // 事件触发时,更新 DataContext 为新实例 DataContext = MainViewModel.Ins; } // 窗口关闭时解订阅(避免内存泄漏) protected override void OnClosed(EventArgs e) { base.OnClosed(e); MainViewModel.InstanceChanged -= OnMainViewModelInstanceChanged; } ``` ### 原因2:事件处理逻辑未正确更新绑定源 即使订阅了事件,若事件处理中未正确更新 `DataContext`(如未重新赋值),或界面绑定路径未指向新实例的属性,界面仍无法更新。 #### 问题场景示例 ```csharp // 事件处理中未更新 DataContext(错误示例) private void OnMainViewModelInstanceChanged() { // 仅调用 Refresh() 无法更新绑定源 (DataContext as MainViewModel)?.RaisePropertyChanged(); } ``` #### 解决方案:强制重新绑定新实例 事件处理中必须显式将 `DataContext` 设置为新的 `MainViewModel.Ins` 实例,确保绑定路径指向新实例: ```csharp private void OnMainViewModelInstanceChanged() { DataContext = null; // 先置空,强制解绑 DataContext = MainViewModel.Ins; // 重新绑定新实例 } ``` ### 原因3:`Ins` 赋值操作在非UI线程执行 若反序列化和 `Ins = result.MainVM` 操作在后台线程执行,`InstanceChanged` 事件会在非UI线程触发,导致 `DataContext` 更新操作(需UI线程)失败,界面无响应。 #### 问题代码示例(后台线程赋值) ```csharp case "Open": Task.Run(() => // 后台线程执行反序列化 { if (JsonSerialization.Deserialize("example.json", out ViewModelContainer result)) { MainViewModel.Ins = result.MainVM; // 后台线程赋值,触发 InstanceChanged 事件 } }); break; ``` #### 解决方案:确保在UI线程赋值 使用 `Dispatcher.Invoke` 将 `Ins` 赋值操作切换到UI线程执行: ```csharp case "Open": if (JsonSerialization.Deserialize("example.json", out ViewModelContainer result)) { // 切换到UI线程赋值 Application.Current.Dispatcher.Invoke(() => { MainViewModel.Ins = result.MainVM; // UI线程触发 InstanceChanged }); } break; ``` ### 原因4:界面绑定路径未动态关联新实例 若界面绑定路径为 `{Binding PropertyName}`(直接绑定 `DataContext` 的属性),当 `DataContext` 被替换为新实例时,WPF 会自动重新解析路径。但如果绑定路径为 `{Binding MainViewModel.Ins.PropertyName}`(静态属性路径),WPF 无法感知 `Ins` 实例的变更,需额外处理。 #### 问题绑定示例(静态路径) ```xml <!-- 错误:绑定路径依赖静态属性,无法自动更新 --> <TextBlock Text="{Binding MainViewModel.Ins.EndOnePose}" /> ``` #### 解决方案:使用动态绑定路径 将界面绑定路径改为直接绑定 `DataContext` 的属性(如 `EndOnePose`),并确保 `DataContext` 是 `MainViewModel` 实例: ```xml <!-- 正确:绑定 DataContext 的属性,DataContext 更新后自动解析 --> <TextBlock Text="{Binding EndOnePose}" /> ``` ### 原因5:新实例的属性未触发 `PropertyChanged` 事件 即使 `DataContext` 指向新实例,若新实例的属性(如 `EndOnePose`)未正确实现 `INotifyPropertyChanged`(例如 `Set` 方法未触发事件),界面仍无法感知属性变更。 #### 验证方法 在 `EndOnePose` 的 `setter` 中添加日志,确认反序列化后是否触发了 `PropertyChanged` 事件: ```csharp public double EndOnePose { get => _EndOnePose; set { if (Set(ref _EndOnePose, value)) { Debug.WriteLine($"EndOnePose 已更新,触发 PropertyChanged 事件"); } } } ``` 若日志未输出,需检查 `NotifyPropertyBase` 的 `Set` 方法是否正确调用了 `RaisePropertyChanged`。 ### 总结:关键排查步骤 1. **确认事件订阅**:界面是否订阅了 `InstanceChanged` 事件; 2. **检查事件处理**:事件处理中是否显式更新了 `DataContext`; 3. **验证线程**:`Ins` 赋值是否在UI线程执行; 4. **绑定路径**:界面绑定是否依赖动态 `DataContext` 而非静态属性; 5. **属性通知**:新实例的属性是否正确触发 `PropertyChanged` 事件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值