我有一个类是一次性 UI 控件。它订阅模型对象的更改以重绘其内容。
另一方面,在某些情况下,同一模型对象的某些特殊更改会指示包含此控件的视图将其删除并处置(控件)。
结果,模型中的更改取决于订阅顺序-首先导致控制处置,然后是方法调用-最终以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!
}
}
在给定的示例中,您会在哪里提出修复建议?Model
和Logic
类只是在不知道事件订阅顺序的情况下做他们的事情。我也没有看到任何设计缺陷View
和Control
实现。
想象一下,有三个不同的团队在实施Model
,Logic
并且UI
不仅有这四个组件,而且还有数百个。这个问题可能在任何地方发生。
在这种特殊情况下,我正在寻找的不是本地修复,但我想找到一种模式来防止这种情况发生。例如:“控件必须优雅地忽略已处置实例上的事件调用”或“逻辑必须阻止模型上的订阅,仅允许 UI 这样做。” 等等
除了接受的答案
是的,已处置的对象事件回调不应引发异常。更一般地说:
...即使在事件被取消订阅后,事件处理程序也必须在被调用时保持健壮。
这有很多原因 - 请参阅 Eric Lippert 的精彩文章Event and Races