从Dispose()触发事件是否可以?

时间:2010-07-15 04:12:45

标签: c# .net transactions idisposable

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

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

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

由于我正在使用Dispose()来确认某个事务是否还有从此处触发这些事件的错误?或者,除了Confirm()清除临时数据之外,在我的界面上使用单独的Dispose()方法来触发事件会更好吗?我想不出任何我想要确认但不处理交易的情况。然而,我并不完全清楚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; }
}

5 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:3)

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

虽然IDisposable.Dispose()方法不是“真正的”析构函数或终结器,但如果其他对象在处理事件期间维护(或可能甚至采用)对处置对象的引用,则它可能会对对象的生命周期产生负面影响。如果您对如何实施此类系统非常小心,可以减轻可能的副作用。但是,重要的是要意识到这样的实现提供的潜力......例如,恶意编码器的攻击面越来越大,例如,保持您的事务对象无限期地存活。

答案 2 :(得分:1)

4年前就已经问过这个问题,但不满足于我正在添加的答案,它将答案和评论中讨论的一些要点与其他方面结合起来。

<强>最后确定: 正如@jrista指出的那样,让我们​​清楚IDisposable与GC或Finalization本身无关 - 这只是一个惯例,强烈推荐的做法。然而,使用Dispose pattern你可以从Finalizer调用Dispose方法(比如@Stephen Cleary指出)。在这种情况下,你绝对不应该提出任何事件,nor should it access other managed objects

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

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

最少惊喜原则: “滥用”Dispose进行交易是一个好主意吗?我会说不,虽然有先例。以Stream为例。通常Stream.Dispose被实现为调用Flush,从而将数据提交给底层媒体。但请注意,我们还有一个明确的Flush方法,因此您应该添加它。我发现“disposing to commit”违反了最少惊喜的原则,明确的Commit方法更加清晰(如果这是您想要的默认行为,您仍然可以从Dispose调用此方法)。

事件级联/无效对象状态:我认为这是不在Dispose中引发事件的最强参数。事件有级联趋势(即一个事件触发其他事件和代码),如果你不小心,你可能会遇到一些代码决定回调到被处理的对象是个好主意的情况。因此可能处于无效状态。调试没有乐趣,特别是如果多个线程可以访问对象!尽管如此,有一些先例,例如Component.Disposed

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

答案 3 :(得分:0)

处理应该只是清理。我会实现Confirm()和Rollback()方法,如果在没有调用dispose的情况下调用dispose,那么它至少应该记录一个错误。

答案 4 :(得分:0)

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