c# - 处置的控件是否应该能够安全地忽略事件回调?

标签 c# .net winforms idisposable

我有一个类是一次性 UI 控件。 它订阅模型对象的更改以重绘其内容。

另一方面,在某些情况下,同一模型对象的某些特殊更改会指示包含此控件的 View 删除并处置它(控件)。

因此模型发生变化,具体取决于订阅顺序 - 首先导致控件处置,然后是方法调用 - 以 ObjectDisposedException 结束。

问题:控件是否应该设计为安全地忽略事件回调,或者我们是否应该尝试防止来自其他层的这种调用

对于那些喜欢看更多代码而不是文字的人,我准备了一个非常简单的例子:

//############################################
class View
{
    private Control m_Control;

    public View(Logic logic, Model model)
    {
        m_Control = new Control(model);
        logic.Changed += LogicChanged;
    }

    private void LogicChanged(object sender, EventArgs e)
    {
        m_Control.Dispose();
        m_Control = null;
    }
}

//############################################
class Control : IDisposable
{
    private readonly Model m_Model;

    public Control(Model model)
    {
        m_Model = model;
        m_Model.Changed += ModelOnChanged;
    }

    public bool IsDisposed { get; private set; }

    public void Dispose()
    {
        m_Model.Changed -= ModelOnChanged;
        IsDisposed = true;
    }

    private void ModelOnChanged(object sender, EventArgs e)
    {
        if (IsDisposed)
        {
            throw new ObjectDisposedException(ToString());
        }
        //Do something
    }
}

//############################################
class Model
{
    public event EventHandler<EventArgs> Changed;

    private void OnChanged(EventArgs e)
    {
        EventHandler<EventArgs> handler = Changed;
        if (handler != null)
            handler(this, e);
    }

    public void Change()
    {
        OnChanged(null);
    }
}

//############################################
class Logic
{
    private readonly Model m_Model;

    public Logic(Model model)
    {
        m_Model = model;
        m_Model.Changed += ModelOnChanged;
    }

    private void ModelOnChanged(object sender, EventArgs e)
    {
        OnChanged(null);
    }

    public event EventHandler<EventArgs> Changed;

    private void OnChanged(EventArgs e)
    {
        EventHandler<EventArgs> handler = Changed;
        if (handler != null)
            handler(this, e);
    }
}

//############################################
class Program
{
    private static void Main(string[] args)
    {
        var model = new Model();
        var logic = new Logic(model);

        var view = new View(logic, model);
        model.Change();
        //And crash!
    }
}

在给定的示例中,您会在哪里提出修复建议? ModelLogic 类只是在不知道事件订阅的顺序的情况下处理它们的业务。我还发现 ViewControl 实现没有设计缺陷。

想象一下,有三个不同的团队在实现 ModelLogicUI,并且不仅有这四个组件,还有数百个组件。该问题可能随处发生。

在这种特殊情况下,我正在寻找的不是本地修复,但我想找到一种模式来防止这种情况发生。例如:“控件必须优雅地忽略对处置实例的事件调用”或“逻辑必须阻止对模型的订阅,只允许 UI 这样做。”等


除了接受的答案

是的,处置的对象事件回调不应该抛出异常。 更一般地说:

... event handlers are required to be robust in the face of being called even after the event has been unsubscribed.

这有很多原因 - 请参阅 Eric Lippert 的精彩文章 Events and Races

最佳答案

在决定是否抛出 ObjectDisposedException 时,应该考虑几个因素:

  1. 对象能否满足特定方法调用的约定,而不必使用不再可用的资源?
  2. 如果函数成功返回,调用者是否可能期望做一些没有必要资源就无法完成的事情,这意味着无论如何失败是不可避免的,所以应该早点抛出而不是晚抛出,或者即使没有抛出异常,调用者也可能做正确的事情?

在许多情况下,尤其是在“更新事件”场景中,调用者并不特别关心被调用方法的作用;调用的语义基本上是“做任何你认为需要做的事情”。这样的操作可以由任何对象成功执行,即使它已被处置,只需决定不需要做任何事情。如果正在使用的回调模式不允许事件处理程序通知事件发布者它不希望接收更多事件(Microsoft 的正常事件模式不提供任何此类设施),则 Disposed 对象可能是继续接收回调,因为事件发布者处于错误状态,但抛出异常可能不会对解决该问题有多大帮助,而且可能会产生更多问题。

关于c# - 处置的控件是否应该能够安全地忽略事件回调?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11635833/

相关文章:

c# - .NET Core Task.Delay() 消耗处理器

c# - 刷新窗口窗体,即使不是事件窗口

c# - 在字符串中搜索字符串(搜索 HTML 源中的所有 href)

c# - 使用这个结构有什么问题吗?

c# - 保存设置时未保存 OrderedDictionary

c# - 在 Selenium C# 中使用 PageFactory/FindsBy 时如何初始化 SelectElements?

c# - 如何为各种构造函数做Activator.CreateInstance?

c# - LINQ to SQL - 动态表属性

c# - 当 RegisterEventBus "No service for type Autofac.ILifetimeScope' 已注册时。

.net - Moq - 设置属性以从方法参数返回字符串