【问题标题】:Is it ok to fire events from Dispose()?可以从 Dispose() 触发事件吗?
【发布时间】:2010-07-15 04:12:45
【问题描述】:

在我当前的项目中,我正在使用实现以下ITransaction 接口的类,如下所示。这是可以撤消的事务的通用接口。我还有一个TransactionSet 类,用于尝试多个事务或事务集,最终可用于创建事务树。

ITransaction 的某些实现保留对对象实例或文件的临时引用,如果有对 Undo() 的调用,它可能会在以后使用。稍后可以确认成功的交易,之后不再允许Undo(),因此也不再需要临时数据。目前我使用Dispose() 作为我的确认方法来清理任何临时资源。

但是,现在我希望我的事务也触发事件以通知其他类发生了什么。除非交易得到确认,否则我不希望事件触发。因为我不想让事务通过撤消然后再次运行来多次触发事件。

既然我使用Dispose() 来确认交易,那么同时触发这些事件有什么问题吗?或者,除了清理临时数据的Dispose() 之外,在我的界面上有一个单独的Confirm() 方法来触发事件会更好吗?我想不出任何我想确认但不想处理交易的情况。然而,我并不完全清楚在 Dispose() 内我应该做什么和不应该做什么。

public enum TransactionStatus
{
    NotRun, // the Transaction has not been run, or has been undoed back to the original state
    Successful, ///the action has been run and was successful
    Error //there was an attempt to run the action but it failed
}

/// <summary>
/// Generic transaction interface
/// </summary>
public interface ITransaction
{
    TransactionStatus Status { get; }

    /// <summary>
    /// Attempts the transaction returns true if successful, false if failed.
    /// If failed it is expected that everything will be returned to the original state.
    /// Does nothing if status is already Successful
    /// </summary>
    /// <returns></returns>
    bool Go();

    /// <summary>
    /// Reverts the transaction
    /// Only does something if status is successful.
    /// Should return status to NotRun
    /// </summary>
    void Undo();

    /// <summary>
    /// A message describing the cause of the error if Status == Error
    /// Otherwise equal String.Empty
    /// </summary>
    string ErrorMessage { get; }
}

【问题讨论】:

    标签: c# .net transactions idisposable


    【解决方案1】:

    Dispose 不是一个特殊的方法——它不像一个 ctor 或终结器或任何东西——它只是一个有用的模式来通知消费者使用它完成的对象。它没有理由不能引发事件。

    【讨论】:

    • +1。是的,Dispose() 不是终结者;在那里做任何你想做的事。
    • +1。请注意,实现IDisposable 的“推荐”模式具有Dispose()Dispose(bool) 方法。当Dispose(false) 被调用时,方法从终结器中调用,并且事件不能被引发。
    【解决方案2】:

    IDisposable 只是一种运行时集成的设计模式,它以比最终确定更有效的方式促进对象清理。在处理方法中你“不能”做的事情很少,但是你应该警惕做一些事情。

    虽然IDisposable.Dispose() 方法不是“真正的”析构函数或终结器,但如果其他对象在处置事件期间维护(甚至可能获取)对处置对象的引用,它可能会对对象的生命周期产生不利影响。如果您对如何实施这样的系统小心谨慎,则可以减轻可能的副作用。但是,重要的是要意识到这种实现提供的潜力......例如增加了恶意编码人员可以利用的攻击面,例如,让您的事务对象无限期地保持活动状态。

    【讨论】:

    • 许多 .net 类都有一个“Disposing”事件,如果没有这个事件将很难使用。如果一个对象将持有对 IDisposable 的引用,该引用可能会或可能不会在其他地方使用(例如,持有 BackgroundImage 的控件),则可以使用 Disposing 事件来允许主对象的持有者在必要时清理嵌套对象。
    【解决方案3】:

    知道这个问题是 4 年前提出的,但不满足于我添加的答案,它结合了答案和 cmets 中讨论的一些要点与其他方面。

    定稿: 正如@jrista 指出的那样,让我们​​明确一点, IDisposable 与 GC 或 Finalization 本身无关 - 这只是一种约定和强烈推荐的做法。使用Dispose pattern 但是您可以从终结器调用 Dispose 方法(如@Stephen Cleary 指出的那样)。在这种情况下,您绝对不应引发任何事件,nor should it access other managed objects 就此而言。

    将 Dispose/Finalizer 问题放在一边,因为您的类不需要 Finalizer,因为它们不包装非托管资源,但还有其他问题。

    内存泄漏/寿命匹配: 这是一个经常被引用的事件问题,也可能适用于您的事务实现。当您的事件发布者的生命周期超过事件订阅者的生命周期时,如果该订阅者没有取消订阅该事件,您可能会发生内存泄漏,因为发布者会继续持有它。如果您的事务的生命周期相当长,并且您为它们订阅了许多短期对象,那么您应该考虑在这些对象中实现 dispose,然后从事务中取消订阅。见Should I always disconnect event handlers in the Dispose method?

    最小意外原则: “滥用” Dispose 提交事务是个好主意吗?我会说不,尽管有先例。以Stream 为例。通常Stream.Dispose 被实现为调用Flush,从而将数据提交到底层介质。但是,请注意,我们仍然有一个显式的 Flush 方法,所以您应该添加它。我发现“准备提交”违反了最小意外原则,显式的Commit 方法更清晰(如果这是您想要的默认行为,您仍然可以从Dispose 调用它)。

    事件级联/无效对象状态:我认为这是在Dispose 中不引发事件的最有力论据。事件倾向于级联(即一个事件触发其他事件和代码),如果您不小心,您可能最终会遇到某些代码决定回调正在处理的对象是个好主意的情况因此可能处于无效状态。调试起来没有乐趣,尤其是当对象可能被多个线程访问时!不过,类似Component.Disposed 这样的先例。

    我建议不要从 Dispose 方法引发事件。当你结束事件发布者的生命周期时,它的所有订阅者相应地更新他们的状态真的很重要吗?我发现在大多数情况下,无论如何我都会摆脱整个对象图(即发布者的寿命比订阅者的寿命长)。在某些情况下,您可能还希望主动抑制在处置期间发生的任何故障情况(例如,关闭 TCP 连接时)。

    【讨论】:

      【解决方案4】:

      Dispose 应该简单地清理。我会实现 Confirm() 和 Rollback() 方法,如果在没有先调用其中任何一个的情况下调用了 dispose,那么这是一个至少应该记录的错误。

      【讨论】:

        【解决方案5】:

        当然,您可以在 Dispose 方法中触发任何事件。但是,如果您想触发事件以确认交易存在,我认为您应该有一个单独的方法来触发事件。 Dispose() 是清理内部资源或将内部实例作为众所周知的模式处理的方法。处理后,您的事务安装不应该存在或不再使用。因此,您可以考虑使用单独的方法来确认临时文件不可用,并在 Transaction 中使用标记或状态来指示。

        【讨论】:

          猜你喜欢
          • 2012-01-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-02-03
          相关资源
          最近更新 更多