【问题标题】:Is it possible to detect if an exception occurred before I entered a finally block in .NET?是否可以在我在 .NET 中输入 finally 块之前检测是否发生异常?
【发布时间】:2015-08-27 15:08:04
【问题描述】:

Java 也有同样的问题,但我对 .NET 的答案很感兴趣。

考虑以下代码:

class Program
{
    static void Main()
    {
        try
        {
            RunTransaction();
            // If there was an exception within the transaction,
            // I won't be here anymore. But if the transaction was
            // cancelled without an exception being thrown, I really
            // need to know because I must stop here anyway.
            OtherCode();
        }
        catch (Excexption ex)
        {
            // Log the exception...
            // If an exception was thrown in the transaction scope,
            // this must be logged here. If a "helper" exception was
            // created in the Dispose method, this may be logged, but
            // it won't say much. It just made sure that nothing else
            // was executed in this try block.
        }
    }

    static void RunTransaction()
    {
        using (var trans = new Transaction())
        {
            // An error may occur here and it should be logged.
            throw new Exception();
            // Maybe the scope is simply left without an exception.
            return;
            // Otherwise, the transaction is committed.
            trans.Commit();
        }
    }
}

class Transaction : IDisposable
{
    bool isCommitted;

    public void Commit()
    {
        isCommitted = true;
    }

    public void Dispose()
    {
        if (!isCommitted)
        {
            // Was an exception thrown before this is called?
            // If not, I might consider throwing one here.
            // I can't always throw an exception here because if
            // another exception is already propagated, it would
            // be dropped and the real error cause would not be
            // visible anymore.
        }
    }
}

Transaction.Dispose方法中,如何知道是否已经抛出异常?

请注意,finally 块并未在此处显式显示,而是隐藏在调用IDisposable.Dispose 方法的using 语句中,此处显示。

更新:我的背景是我有一个事务包装类,其行为有点像TransactionScope。但是TransactionScope 太神奇了,不能按预期工作,所以我回到了真正的数据库事务。有些方法需要事务,但如果从另一个已经需要事务的方法调用它们,则内部“事务”必须“加入”外部事务,而不是从数据库请求新的嵌套事务,这在我的任何地方都不支持了解。实际代码比我的示例要复杂一些,其中内部事务可能会被取消,从而有效地结束事务。然后,如果任何东西继续在外部事务中运行,它不再存在,它就不能回滚,但会在任何事务之外有效地运行!必须采取一切手段防止这种情况发生。首先解决一个异常就可以了,但是如果没有这个,内部事务也可以被取消。这是我想在我的作用域助手类中检测到的。

【问题讨论】:

  • 按 ctrl+alt+e 并标记 clr exception 复选框
  • 如果你想知道交易是否被取消,为什么不从方法中获得一个返回值来表明它是否被取消?
  • 您到底想达到什么目的?某种保护措施以确保没有代码从事务块返回而不提交?
  • 我很难理解你想用这个做什么。如果发生异常,您可以在外部记录异常。你不应该把Dispose()扔进去。我可能遗漏了一些东西,但这会给你带来什么?

标签: c# .net exception


【解决方案1】:
public void Dispose()
{
    if (!isCommitted)
    {
        // Was an exception thrown before this is called?
        // If not, I might consider throwing one here.
        // I can't always throw an exception here because if
        // another exception is already propagated, it would
        // be dropped and the real error cause would not be
        // visible anymore.
    }
}

你说你想从 Dispose 抛出一个异常,如果还没有抛出的话。

但是Dispose 不应该抛出异常。来自Implementing a Dispose method

为了帮助确保始终适当地清理资源,Dispose 方法应该可以多次调用而不会引发异常

同样来自Dispose Pattern

避免在 Dispose(bool) 内引发异常,除非在包含进程已损坏的危急情况下(泄漏、不一致的共享状态等)。

用户希望对Dispose 的调用不会引发异常。

如果Dispose 可以引发异常,则不会执行进一步的finally 块清理逻辑。为了解决这个问题,用户需要将每个对Dispose 的调用(在 finally 块中!)包装在一个 try 块中,这会导致非常复杂的清理处理程序。如果执行 Dispose(bool disposing) 方法,则在 disposing 为 false 时永远不要抛出异常。如果在终结器上下文中执行,这样做将终止进程。

【讨论】:

  • OP 想知道之前 Dispose()是否触发了异常。
  • @JohnnyMopp 是的,他想知道因为如果还没有抛出异常,他想从Dispose 抛出异常。但是由于Dispose 不应该抛出异常,所以这一点没有实际意义。
  • 虽然文档是正确合理的,但我的 Dispose 方法不是释放资源,而是实现using IDisposable 模式。我也可以写try/finally,没有IDisposable 接口,它的工作原理是一样的。它只是保持代码清洁的帮手。 C# 糖。
  • @LonelyPixel 那我会说你在滥用这个功能。
  • 也许吧。你能想象我使用的是 try/finally 吗?问题依旧。
【解决方案2】:

您需要问自己的问题是“为什么这个对象有任何业务知道是否有异常?”。也许我在这里错了,但似乎是因为您认为RunTransaction() 与事务本身有关,这是一个错误的假设,因为代码似乎位于Transaction 类之外。

你应该重构你的代码的方式是:

class Transaction : IDisposable
{
    bool isCommitted;
    public void Commit() { ... }
    public void Dispose() { ... }
    public void RunTransaction() { ... }
}

这样如果RunTransaction() 抛出,你就知道了。

编辑:或者,如果代码必须位于 Transaction 类之外,您可以进一步重构 Transaction 来做:

    public void RunTransaction(Action action) { ... }

并调用它:

    trans.RunTransaction(() => RunTransaction());

【讨论】:

  • 我同意你的第一段,但我认为你的代码示例被误导了。 RunTransaction() 可能只是执行事务代码的所有函数的占位符 - 它可能不是运行某些事务的通用包装器,因此它不应该是 Transaction 的一部分。
  • 这是简化的示例代码。我无法重构它,因为它派生的真实代码不允许重构,因为您的假设在那里不再有效。
猜你喜欢
  • 2010-09-16
  • 2017-09-04
  • 2012-05-30
  • 1970-01-01
  • 2013-02-23
  • 2011-07-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多