【问题标题】:Should you implement IDisposable.Dispose() so that it never throws?您是否应该实现 IDisposable.Dispose() 以便它永远不会抛出?
【发布时间】:2009-02-23 13:24:38
【问题描述】:

对于 C++ 中的等效机制(析构函数),建议是 it should usually not throw any exceptions。这主要是因为这样做您可能会终止您的进程,这很少是一个好的策略。

在 .NET 中的等效场景中...

  1. 第一个异常被抛出
  2. 由于第一个异常而执行 finally 块
  3. finally 块调用 Dispose() 方法
  4. Dispose() 方法引发第二个异常

...您的进程不会立即终止。但是,您会丢失信息,因为 .NET 毫不客气地将第一个异常替换为第二个异常。因此,调用堆栈上方某处的 catch 块将永远不会看到第一个异常。但是,人们通常对第一个异常更感兴趣,因为这通常会提供更好的线索来说明事情开始出错的原因。

由于 .NET 缺乏一种机制来检测是否在异常挂起时正在执行代码,因此 IDisposable 的实现方式似乎只有两种选择:

  • 始终吞下 Dispose() 中发生的所有异常。不好,因为您最终可能还会吞下 OutOfMemoryException、ExecutionEngineException 等,我通常宁愿在它们发生时将其拆除,而没有另一个尚未挂起的异常。
  • 让所有异常都从 Dispose() 传播出去。不好,因为您可能会丢失有关问题根本原因的信息,请参见上文。

那么,两害相权取其轻?有没有更好的办法?

编辑:澄清一下,我不是在谈论是否主动从 Dispose() 抛出异常,我是在谈论让 Dispose() 调用的方法抛出的异常传播到 Dispose () 与否,例如:

using System;
using System.Net.Sockets;

public sealed class NntpClient : IDisposable
{
    private TcpClient tcpClient;

    public NntpClient(string hostname, int port)
    {
        this.tcpClient = new TcpClient(hostname, port);
    }

    public void Dispose()
    {
        // Should we implement like this or leave away the try-catch?
        try
        {
            this.tcpClient.Close(); // Let's assume that this might throw
        }
        catch
        {
        }
    }
}

【问题讨论】:

  • 对于上下文:WCF 是臭名昭著的...

标签: c# .net idisposable


【解决方案1】:

Framework Design Guidelines (2nd ed) 有这个 (§9.4.1):

避免在 Dispose(bool) 中抛出异常,除非在关键情况下 包含进程已损坏的情况(泄漏,不一致 共享状态等)。

评论[编辑]:

  • 有指导方针,而不是硬性规则。这是一个“避免”而不是“不要”的指导方针。如前所述(在 cmets 中),该框架在某些地方打破了这个(和其他)准则。诀窍是知道何时打破准则。在很多方面,这就是熟练工和大师之间的区别。
  • 如果清理的某些部分可能失败,则应提供一个 Close 方法,该方法将引发异常,以便调用者可以处理它们。
  • 如果你遵循 dispose 模式(如果类型直接包含一些非托管资源,你应该这样做),那么 Dispose(bool) 可能会从终结器中调用,从终结器中抛出是一个坏主意,并且会阻止其他对象正在敲定。

我的观点:从 Dispose 逃逸的异常应该只是那些,如指南中所述,具有足够的灾难性,以至于当前进程无法提供进一步的可靠功能。

【讨论】:

  • 感谢您的指点。问题是框​​架本身不遵循这个准则,请参阅 Marc 的回答。
  • LINQ 违反了指导方针,这在评论中有所说明。请记住,准则并非牢不可破的规则,这是一条应避免的准则,而不是不要的准则。
  • 好的,那你有什么推荐的?抓住一切还是一无所获?
  • 默认;让任何人逃跑。如果包含在终结器中的操作很可能会失败(例如关闭网络资源),则提供单独的 Close 方法。
  • 我喜欢放置一个 Close 方法来抛出和 Dispose 不抛出的想法。如果可以的话 +2。
【解决方案2】:

我认为在这种情况下吞咽是两害相权取其轻,因为最好提高 原始 Exception - 警告:除非,也许未能干净地处理本身就非常关键(也许如果 TransactionScope 无法处理,因为这可能表明回滚失败)。

请参阅here 了解更多关于此的想法 - 包括包装/扩展方法的想法:

using(var foo = GetDodgyDisposableObject().Wrap()) {
   foo.BaseObject.SomeMethod();
   foo.BaseObject.SomeOtherMethod(); // etc
} // now exits properly even if Dispose() throws

当然,您也可以做一些奇怪的事情,即重新抛出原始和第二个 (Dispose()) 异常的复合异常 - 但请考虑:您可以有多个 using 块......它会很快变得难以管理。实际上,最初的例外是有趣的例外。

【讨论】:

  • 我同意它可能变得无法管理,但重要的一点是原始异常是有趣的除非没有原始异常。我继续写了奇怪的东西并且对此感到非常满意,尽管在我的特殊情况下我最终只是吞下了异常within Dispose(因为我可以修改=代码并且不想拥有访问班级的每个用户)
  • 这是真的,它突出了TransactionScope 设计中的一个弱点。另一方面,事务不能回滚失败,因为即使断电也是默认操作。 TransactionScope 应该在 Dispose 期间吞下异常。话又说回来,Dispose 实际上做了要提交的工作,而 Commit() 只标记要提交,但实际上并没有这样做。也许是另一个设计错误。
【解决方案3】:

Dispose 应该被设计成它的目的,处理对象。此任务是安全的,并且大多数时候不会引发异常。如果您看到自己从Dispose 抛出异常,您可能应该三思而后行,看看您是否在其中做了太多事情。除此之外,我认为Dispose 应该像对待所有其他方法一样对待:如果你能用它做某事,就处理它,如果你不能,让它冒泡。

编辑:对于指定的示例,我将编写代码以便 我的代码 不会导致异常,但清除 TcpClient 可能会导致异常,应该是在我看来有效传播(或作为更通用的异常处理和重新抛出,就像任何方法一样):

public void Dispose() { 
   if (tcpClient != null)
     tcpClient.Close();
}

但是,就像任何方法一样,如果您知道tcpClient.Close() 可能会抛出一个应该被忽略(没关系)或应该由另一个异常对象表示的异常,您可能想要捕获它。

【讨论】:

  • 冒泡会丢失最初破坏的实际东西;重新模式...最好告诉 WCF 团队;-p
  • 我澄清了这个问题。显然,调用 TcpClient.Close() 并没有资格做很多事情,对吧?
【解决方案4】:

释放资源应该是一种“安全”的操作——毕竟我怎样才能从无法释放资源中恢复过来呢?所以从 Dispose 抛出异常是没有意义的。

但是,如果我在 Dispose 内部发现程序状态已损坏,最好抛出异常然后吞下它,最好现在粉碎然后继续运行并产生错误结果。

【讨论】:

  • 如果尝试Dispose TCP 连接成功,代码有权假设已发送的所有内容都已到达另一端。如果尝试失败,代码不应假设信息已被传递,但这并不意味着整个系统状态已损坏。
  • @supercat - 我写道,除非系统状态已损坏,否则释放资源应该是一种安全操作(并且,根据接受的答案,框架设计指南同意我的观点)我从未说过所有类在 .net 中遵循此准则(有些不遵循)。我个人认为您为 TCP 连接描述的行为是错误的(因为“信息已传递”没有任何意义,如果服务器在发送 TCP ACK 后立即崩溃,则数据就像根本没有发送一样丢失) ,另外,我在 TcpClient MSDN 文档中找不到这种行为。
  • TCP 不是一个完美的例子,但很多人都熟悉它;也许一个更好的例子是尝试将文件保存到可能不需要用于任何其他目的的可移动驱动器。如果有人在写入文件之前拔出 U 盘,则不应阻止任何由此产生的异常,但移除 U 盘不应破坏未使用它的应用程序部分 [除其他事项外,因为用户的文档未保存,用户可能需要再次尝试保存!]
  • @supercat - 如果拉出 USB 驱动器导致 Dispose 出现错误,因为 Dispose 试图将先前写入的数据刷新到驱动器,而不是系统状态变得有点损坏(您之前执行的那些 Write 调用并返回成功-它们确实失败了,并且您在假设它们成功的情况下所做的任何事情都可能无效),如果拉动驱动器导致 Dispose 错误,因为 Dispose 执行了不应该执行的文件操作,则 Dispose 用于清理和释放资源(有时会中止)不是为了保存东西。
  • 流延迟写入请求直到流被刷新或关闭是正常的,该操作的成功意味着写入数据的成功。如果创建流的执行范围因异常而退出,则流将没有理由在此之前写入数据,并且除了在Dispose 期间之外没有合适的地方写入数据。
【解决方案5】:

Microsoft 没有向 Dispose 提供 Exception 参数太糟糕了,目的是将其包装为 InnerException 以防处理本身引发异常。可以肯定的是,有效使用这样的参数需要使用 C# 不支持的异常过滤器块,但也许这样的参数的存在可能促使 C# 设计人员提供这样的功能?我希望看到的一个不错的变化是将异常“参数”添加到 finally 块,例如

最后异常 ex: // 在 C# 中 最后 Ex as Exception ' 在 VB

它的行为类似于普通的 finally 块,除了如果 'Try' 运行完成,'ex' 将为 null/Nothing,否则将保持抛出的异常。太糟糕了,没有办法让现有代码使用这样的功能。

【讨论】:

    【解决方案6】:

    Dispose 方法传播或吞下异常有多种策略,可能基于是否也从主逻辑抛出了未经处理的异常。最好的解决方案是将决定权留给调用者,具体取决于他们的具体要求。我已经实现了一个通用的扩展方法,提供:

    • 传播Dispose异常的默认using语义
    • Marc Gravell's suggestion 总是吞下Dispose 异常
    • maxyfc's alternative 仅在主逻辑出现异常时才吞下 Dispose 异常,否则会丢失
    • Daniel Chambers's approach 将多个异常包装到 AggregateException
    • 一种类似的方法,总是将所有异常包装到 AggregateException 中(就像 Task.Wait 所做的那样)

    这是我的扩展方法:

    /// <summary>
    /// Provides extension methods for the <see cref="IDisposable"/> interface.
    /// </summary>
    public static class DisposableExtensions
    {
        /// <summary>
        /// Executes the specified action delegate using the disposable resource,
        /// then disposes of the said resource by calling its <see cref="IDisposable.Dispose()"/> method.
        /// </summary>
        /// <typeparam name="TDisposable">The type of the disposable resource to use.</typeparam>
        /// <param name="disposable">The disposable resource to use.</param>
        /// <param name="action">The action to execute using the disposable resource.</param>
        /// <param name="strategy">
        /// The strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method.
        /// </param>
        /// <exception cref="ArgumentNullException"><paramref name="disposable"/> or <paramref name="action"/> is <see langword="null"/>.</exception>
        public static void Using<TDisposable>(this TDisposable disposable, Action<TDisposable> action, DisposeExceptionStrategy strategy)
            where TDisposable : IDisposable
        {
            ArgumentValidate.NotNull(disposable, nameof(disposable));
            ArgumentValidate.NotNull(action, nameof(action));
            ArgumentValidate.IsEnumDefined(strategy, nameof(strategy));
    
            Exception mainException = null;
    
            try
            {
                action(disposable);
            }
            catch (Exception exception)
            {
                mainException = exception;
                throw;
            }
            finally
            {
                try
                {
                    disposable.Dispose();
                }
                catch (Exception disposeException)
                {
                    switch (strategy)
                    {
                        case DisposeExceptionStrategy.Propagate:
                            throw;
    
                        case DisposeExceptionStrategy.Swallow:
                            break;   // swallow exception
    
                        case DisposeExceptionStrategy.Subjugate:
                            if (mainException == null)
                                throw;
                            break;    // otherwise swallow exception
    
                        case DisposeExceptionStrategy.AggregateMultiple:
                            if (mainException != null)
                                throw new AggregateException(mainException, disposeException);
                            throw;
    
                        case DisposeExceptionStrategy.AggregateAlways:
                            if (mainException != null)
                                throw new AggregateException(mainException, disposeException);
                            throw new AggregateException(disposeException);
                    }
                }
    
                if (mainException != null && strategy == DisposeExceptionStrategy.AggregateAlways)
                    throw new AggregateException(mainException);
            }
        }
    }
    

    这些是已实施的策略:

    /// <summary>
    /// Identifies the strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method
    /// of an <see cref="IDisposable"/> instance, in conjunction with exceptions thrown by the main logic.
    /// </summary>
    /// <remarks>
    /// This enumeration is intended to be used from the <see cref="DisposableExtensions.Using"/> extension method.
    /// </remarks>
    public enum DisposeExceptionStrategy
    {
        /// <summary>
        /// Propagates any exceptions thrown by the <see cref="IDisposable.Dispose"/> method.
        /// If another exception was already thrown by the main logic, it will be hidden and lost.
        /// This behaviour is consistent with the standard semantics of the <see langword="using"/> keyword.
        /// </summary>
        /// <remarks>
        /// <para>
        /// According to Section 8.10 of the C# Language Specification (version 5.0):
        /// </para>
        /// <blockquote>
        /// If an exception is thrown during execution of a <see langword="finally"/> block,
        /// and is not caught within the same <see langword="finally"/> block, 
        /// the exception is propagated to the next enclosing <see langword="try"/> statement. 
        /// If another exception was in the process of being propagated, that exception is lost. 
        /// </blockquote>
        /// </remarks>
        Propagate,
    
        /// <summary>
        /// Always swallows any exceptions thrown by the <see cref="IDisposable.Dispose"/> method,
        /// regardless of whether another exception was already thrown by the main logic or not.
        /// </summary>
        /// <remarks>
        /// This strategy is presented by Marc Gravell in
        /// <see href="http://blog.marcgravell.com/2008/11/dontdontuse-using.html">don't(don't(use using))</see>.
        /// </remarks>
        Swallow,
    
        /// <summary>
        /// Swallows any exceptions thrown by the <see cref="IDisposable.Dispose"/> method
        /// if and only if another exception was already thrown by the main logic.
        /// </summary>
        /// <remarks>
        /// This strategy is suggested in the first example of the Stack Overflow question
        /// <see href="https://stackoverflow.com/q/1654487/1149773">Swallowing exception thrown in catch/finally block</see>.
        /// </remarks>
        Subjugate,
    
        /// <summary>
        /// Wraps multiple exceptions, when thrown by both the main logic and the <see cref="IDisposable.Dispose"/> method,
        /// into an <see cref="AggregateException"/>. If just one exception occurred (in either of the two),
        /// the original exception is propagated.
        /// </summary>
        /// <remarks>
        /// This strategy is implemented by Daniel Chambers in
        /// <see href="http://www.digitallycreated.net/Blog/51/c%23-using-blocks-can-swallow-exceptions">C# Using Blocks can Swallow Exceptions</see>
        /// </remarks>
        AggregateMultiple,
    
        /// <summary>
        /// Always wraps any exceptions thrown by the main logic and/or the <see cref="IDisposable.Dispose"/> method
        /// into an <see cref="AggregateException"/>, even if just one exception occurred.
        /// </summary>
        /// <remarks>
        /// This strategy is similar to behaviour of the <see cref="Task.Wait()"/> method of the <see cref="Task"/> class 
        /// and the <see cref="Task{TResult}.Result"/> property of the <see cref="Task{TResult}"/> class:
        /// <blockquote>
        /// Even if only one exception is thrown, it is still wrapped in an <see cref="AggregateException"/> exception.
        /// </blockquote>
        /// </remarks>
        AggregateAlways,
    }
    

    使用示例:

    new FileStream(Path.GetTempFileName(), FileMode.Create)
        .Using(strategy: DisposeExceptionStrategy.Subjugate, action: fileStream =>
        {
            // Access fileStream here
            fileStream.WriteByte(42);
            throw new InvalidOperationException();
        });   
        // Any Dispose() exceptions will be swallowed due to the above InvalidOperationException
    

    更新:如果您需要支持返回值和/或异步的委托,那么您可以使用这些重载:

    /// <summary>
    /// Provides extension methods for the <see cref="IDisposable"/> interface.
    /// </summary>
    public static class DisposableExtensions
    {
        /// <summary>
        /// Executes the specified action delegate using the disposable resource,
        /// then disposes of the said resource by calling its <see cref="IDisposable.Dispose()"/> method.
        /// </summary>
        /// <typeparam name="TDisposable">The type of the disposable resource to use.</typeparam>
        /// <param name="disposable">The disposable resource to use.</param>
        /// <param name="strategy">
        /// The strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method.
        /// </param>
        /// <param name="action">The action delegate to execute using the disposable resource.</param>
        public static void Using<TDisposable>(this TDisposable disposable, DisposeExceptionStrategy strategy, Action<TDisposable> action)
            where TDisposable : IDisposable
        {
            ArgumentValidate.NotNull(disposable, nameof(disposable));
            ArgumentValidate.NotNull(action, nameof(action));
            ArgumentValidate.IsEnumDefined(strategy, nameof(strategy));
    
            disposable.Using(strategy, disposableInner =>
            {
                action(disposableInner);
                return true;   // dummy return value
            });
        }
    
        /// <summary>
        /// Executes the specified function delegate using the disposable resource,
        /// then disposes of the said resource by calling its <see cref="IDisposable.Dispose()"/> method.
        /// </summary>
        /// <typeparam name="TDisposable">The type of the disposable resource to use.</typeparam>
        /// <typeparam name="TResult">The type of the return value of the function delegate.</typeparam>
        /// <param name="disposable">The disposable resource to use.</param>
        /// <param name="strategy">
        /// The strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method.
        /// </param>
        /// <param name="func">The function delegate to execute using the disposable resource.</param>
        /// <returns>The return value of the function delegate.</returns>
        public static TResult Using<TDisposable, TResult>(this TDisposable disposable, DisposeExceptionStrategy strategy, Func<TDisposable, TResult> func)
            where TDisposable : IDisposable
        {
            ArgumentValidate.NotNull(disposable, nameof(disposable));
            ArgumentValidate.NotNull(func, nameof(func));
            ArgumentValidate.IsEnumDefined(strategy, nameof(strategy));
    
    #pragma warning disable 1998
            var dummyTask = disposable.UsingAsync(strategy, async (disposableInner) => func(disposableInner));
    #pragma warning restore 1998
    
            return dummyTask.GetAwaiter().GetResult();
        }
    
        /// <summary>
        /// Executes the specified asynchronous delegate using the disposable resource,
        /// then disposes of the said resource by calling its <see cref="IDisposable.Dispose()"/> method.
        /// </summary>
        /// <typeparam name="TDisposable">The type of the disposable resource to use.</typeparam>
        /// <param name="disposable">The disposable resource to use.</param>
        /// <param name="strategy">
        /// The strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method.
        /// </param>
        /// <param name="asyncFunc">The asynchronous delegate to execute using the disposable resource.</param>
        /// <returns>A task that represents the asynchronous operation.</returns>
        public static Task UsingAsync<TDisposable>(this TDisposable disposable, DisposeExceptionStrategy strategy, Func<TDisposable, Task> asyncFunc)
            where TDisposable : IDisposable
        {
            ArgumentValidate.NotNull(disposable, nameof(disposable));
            ArgumentValidate.NotNull(asyncFunc, nameof(asyncFunc));
            ArgumentValidate.IsEnumDefined(strategy, nameof(strategy));
    
            return disposable.UsingAsync(strategy, async (disposableInner) =>
            {
                await asyncFunc(disposableInner);
                return true;   // dummy return value
            });
        }
    
        /// <summary>
        /// Executes the specified asynchronous function delegate using the disposable resource,
        /// then disposes of the said resource by calling its <see cref="IDisposable.Dispose()"/> method.
        /// </summary>
        /// <typeparam name="TDisposable">The type of the disposable resource to use.</typeparam>
        /// <typeparam name="TResult">The type of the return value of the asynchronous function delegate.</typeparam>
        /// <param name="disposable">The disposable resource to use.</param>
        /// <param name="strategy">
        /// The strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method.
        /// </param>
        /// <param name="asyncFunc">The asynchronous function delegate to execute using the disposable resource.</param>
        /// <returns>
        /// A task that represents the asynchronous operation. 
        /// The task result contains the return value of the asynchronous function delegate.
        /// </returns>
        public static async Task<TResult> UsingAsync<TDisposable, TResult>(this TDisposable disposable, DisposeExceptionStrategy strategy, Func<TDisposable, Task<TResult>> asyncFunc)
            where TDisposable : IDisposable
        {
            ArgumentValidate.NotNull(disposable, nameof(disposable));
            ArgumentValidate.NotNull(asyncFunc, nameof(asyncFunc));
            ArgumentValidate.IsEnumDefined(strategy, nameof(strategy));
    
            Exception mainException = null;
    
            try
            {
                return await asyncFunc(disposable);
            }
            catch (Exception exception)
            {
                mainException = exception;
                throw;
            }
            finally
            {
                try
                {
                    disposable.Dispose();
                }
                catch (Exception disposeException)
                {
                    switch (strategy)
                    {
                        case DisposeExceptionStrategy.Propagate:
                            throw;
    
                        case DisposeExceptionStrategy.Swallow:
                            break;   // swallow exception
    
                        case DisposeExceptionStrategy.Subjugate:
                            if (mainException == null)
                                throw;
                            break;    // otherwise swallow exception
    
                        case DisposeExceptionStrategy.AggregateMultiple:
                            if (mainException != null)
                                throw new AggregateException(mainException, disposeException);
                            throw;
    
                        case DisposeExceptionStrategy.AggregateAlways:
                            if (mainException != null)
                                throw new AggregateException(mainException, disposeException);
                            throw new AggregateException(disposeException);
                    }
                }
    
                if (mainException != null && strategy == DisposeExceptionStrategy.AggregateAlways)
                    throw new AggregateException(mainException);
            }
        }
    }
    

    【讨论】:

    • 我非常喜欢这个,但在 C# 6 中可以使用异常过滤对其进行改进。在外部尝试中,您可以使用catch (Exception exception) when (null == (mainException = exception)) {} 来获取主要异常,catch (Exception exception) when (strategy == DisposeExceptionStrategy.AggregateAlways) 来聚合主要异常,在内部尝试中使用catch when (strategy == DisposeExceptionStrategy.Swallow || (strategy == DisposeExceptionStrategy.Subject &amp;&amp; mainException != null)) {} 等。然后您的所有堆栈跟踪都将包括对action 的实际调用和/或Dispose,而不是重新抛出。
    【解决方案7】:

    我可能会使用日志记录来捕获有关第一个异常的详细信息,然后允许引发第二个异常。

    【讨论】:

      【解决方案8】:

      这是一种相当干净地抓取usingDispose 的内容引发的任何异常的方法。

      原码:

      using (var foo = new DisposableFoo())
      {
          codeInUsing();
      }
      

      如果codeInUsing() throws 或foo.Dispose() throws 或两者都抛出,则下面的代码将抛出,并让您看到第一个异常(有时包装为InnerExeption,具体取决于):

      var foo = new DisposableFoo();
      Helpers.DoActionThenDisposePreservingActionException(
          () =>
          {
              codeInUsing();
          },
          foo);
      

      这不是很好,但也不算太糟糕。

      这是实现此功能的代码。我将其设置为在未附加调试器时按描述工作,因为当附加调试器时,我更担心它会在第一个异常时在正确的位置中断。您可以根据需要进行修改。

      public static void DoActionThenDisposePreservingActionException(Action action, IDisposable disposable)
      {
          bool exceptionThrown = true;
          Exception exceptionWhenNoDebuggerAttached = null;
          bool debuggerIsAttached = Debugger.IsAttached;
          ConditionalCatch(
              () =>
              {
                  action();
                  exceptionThrown = false;
              },
              (e) =>
              {
                  exceptionWhenNoDebuggerAttached = e;
                  throw new Exception("Catching exception from action(), see InnerException", exceptionWhenNoDebuggerAttached);
              },
              () =>
              {
                  Exception disposeExceptionWhenExceptionAlreadyThrown = null;
                  ConditionalCatch(
                      () =>
                      {
                          disposable.Dispose();
                      },
                      (e) =>
                      {
                          disposeExceptionWhenExceptionAlreadyThrown = e;
                          throw new Exception("Caught exception in Dispose() while unwinding for exception from action(), see InnerException for action() exception",
                              exceptionWhenNoDebuggerAttached);
                      },
                      null,
                      exceptionThrown && !debuggerIsAttached);
              },
              !debuggerIsAttached);
      }
      
      public static void ConditionalCatch(Action tryAction, Action<Exception> conditionalCatchAction, Action finallyAction, bool doCatch)
      {
          if (!doCatch)
          {
              try
              {
                  tryAction();
              }
              finally
              {
                  if (finallyAction != null)
                  {
                      finallyAction();
                  }
              }
          }
          else
          {
              try
              {
                  tryAction();
              }
              catch (Exception e)
              {
                  if (conditionalCatchAction != null)
                  {
                      conditionalCatchAction(e);
                  }
              }
              finally
              {
                  if (finallyAction != null)
                  {
                      finallyAction();
                  }
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-02-13
        • 1970-01-01
        • 2021-12-13
        • 2011-01-29
        • 2015-09-10
        • 2017-08-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多