private static async Task TestStrategyAsync <TException>(DisposeExceptionStrategy strategy, bool throwMain, bool throwDispose, Action <TException> exceptionVerify = null)
            where TException : Exception
        {
            // No return value:

            var disposable = new SampleDisposable(throwDispose);

            var exception = await ExceptionAssert.ThrowsAsync <TException>(() =>
                                                                           disposable.UsingAsync(strategy, async _ => await RunMainLogicAsync(throwMain)));

            Assert.IsTrue(disposable.WasDisposeCalled);
            exceptionVerify?.Invoke(exception);

            // With return value:

            disposable = new SampleDisposable(throwDispose);

            exception = await ExceptionAssert.ThrowsAsync <TException>(() =>
                                                                       disposable.UsingAsync(strategy, async _ =>
            {
                await RunMainLogicAsync(throwMain);
                return(42);
            }));

            Assert.IsTrue(disposable.WasDisposeCalled);
            exceptionVerify?.Invoke(exception);
        }
        private static void TestStrategy <TException>(DisposeExceptionStrategy strategy, bool throwMain, bool throwDispose, Action <TException> exceptionVerify = null)
            where TException : Exception
        {
            // No return value:

            var disposable = new SampleDisposable(throwDispose);

            var exception = ExceptionAssert.Throws <TException>(() =>
                                                                disposable.Using(strategy, _ => RunMainLogic(throwMain)));

            Assert.IsTrue(disposable.WasDisposeCalled);
            exceptionVerify?.Invoke(exception);

            // With return value:

            disposable = new SampleDisposable(throwDispose);

            exception = ExceptionAssert.Throws <TException>(() =>
                                                            disposable.Using(strategy, _ =>
            {
                RunMainLogic(throwMain);
                return(42);
            }));

            Assert.IsTrue(disposable.WasDisposeCalled);
            exceptionVerify?.Invoke(exception);
        }
 /// <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 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="asyncAction">The asynchronous action delegate to execute using the disposable resource.</param>
        /// <returns>A task that represents the asynchronous operation.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="disposable"/> or <paramref name="asyncAction"/> is <see langword="null"/>.</exception>
        /// <remarks>
        /// Refer to the remarks on the <see cref="Using{TDisposable}(TDisposable, DisposeExceptionStrategy, Action{TDisposable})"/> overload.
        /// </remarks>
        public static Task UsingAsync <TDisposable>(this TDisposable disposable, DisposeExceptionStrategy strategy, Func <TDisposable, Task> asyncAction)
            where TDisposable : IDisposable
        {
            ArgumentValidate.NotNull(disposable, nameof(disposable));
            ArgumentValidate.NotNull(asyncAction, nameof(asyncAction));
            ArgumentValidate.EnumDefined(strategy, nameof(strategy));

            var asyncFunc = asyncAction.ReturnAsync(true);

            return(disposable.UsingAsync(strategy, asyncFunc));
        }
        /// <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>
        /// <exception cref="ArgumentNullException"><paramref name="disposable"/> or <paramref name="action"/> is <see langword="null"/>.</exception>
        /// <remarks>
        /// <para>
        /// This extension method enhances the functionality of the <see langword="using"/> keyword by providing alternative strategies
        /// for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method,
        /// in conjunction with exceptions thrown by the main logic of the <paramref name="action"/> delegate.
        /// </para>
        /// <para>
        /// The standard behavior of the <see langword="using"/> keyword causes any exceptions from the main logic
        /// to be hidden and lost if an exception is also thrown from <see cref="IDisposable.Dispose"/>.
        /// According to Sections 8.9.5 and 8.10 of the C# Language Specification (version 5.0):
        /// </para>
        /// <blockquote>
        /// If the <see langword="finally"/> block throws another exception, processing of the current exception
        /// [that was thrown from the <see langword="try"/> block or a <see langword="catch"/> block] is terminated.
        /// </blockquote>
        /// <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>
        /// <para>
        /// MSDN cautions against the throwing of exceptions from the <see cref="IDisposable.Dispose"/> method.
        /// </para>
        /// <blockquote>
        /// X AVOID throwing an exception from within <see cref="IDisposable.Dispose"/> except under critical situations
        /// where the containing process has been corrupted (leaks, inconsistent shared state, etc.).
        /// Users expect that a call to <see cref="IDisposable.Dispose"/> will not raise an exception.
        /// </blockquote>
        /// <para>
        /// However, this guideline is broken repeatedly throughout the .NET Framework, including in the <see cref="FileStream"/> class,
        /// which can throw <see cref="IOException"/> whilst flushing its buffered data to the underlying device,
        /// and notoriously from the <see href="https://docs.microsoft.com/en-us/dotnet/api/system.servicemodel.clientbase-1">ClientBase&lt;TChannel&gt;</see> base class for WCF clients,
        /// which recommends ditching the <see langword="using"/> keyword to avoid this issue
        /// in <see href="https://docs.microsoft.com/en-us/dotnet/framework/wcf/samples/use-close-abort-release-wcf-client-resources">Close and Abort release resources safely when network connections have dropped</see>.
        /// </para>
        /// <para>
        /// The problematic implications of such exception swallowing are discussed
        /// in this <see href="https://stackoverflow.com/q/577607/1149773">Stack Overflow question</see>:
        /// </para>
        /// <blockquote>
        /// <list type="number">
        /// <item>A first exception is thrown</item>
        /// <item>A finally block is executed as a result of the first exception</item>
        /// <item>The finally block calls a Dispose() method</item>
        /// <item>The Dispose() method throws a second exception</item>
        /// </list>
        /// […] You lose information because .NET unceremoneously replaces the first exception with the second one.
        /// A catch block somewhere up the call stack will therefore never see the first exception.
        /// However, one is usually more interested in the first exception because that normally gives better clues
        /// as to why things started to go wrong.
        /// </blockquote>
        /// <para>
        /// This extension method aims to provide a conclusive resolution to this issue by implementing the various plausible strategies,
        /// including the standard behavior, and leaving it up to the caller to decide which to use.
        /// </para>
        /// <list type="bullet">
        /// <listheader>References</listheader>
        /// <item><see href="https://stackoverflow.com/q/577607/1149773">Should you implement IDisposable.Dispose() so that it never throws?</see>, <i>Stack Overflow</i></item>
        /// <item><see href="https://stackoverflow.com/q/35602760/1149773">Delegate-parameterized method vs IDisposable implementation"</see>, <i>Stack Overflow</i></item>
        /// <item><see href="https://stackoverflow.com/q/1654487/1149773">Swallowing exception thrown in catch/finally block"</see>, <i>Stack Overflow</i></item>
        /// <item><see href="https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose">Implementing a Dispose Method</see>, <i>MSDN Library</i></item>
        /// </list>
        /// </remarks>
        /// <example>
        /// <code>
        /// 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
        /// </code>
        /// </example>
        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.EnumDefined(strategy, nameof(strategy));

            var func = action.Return(true);

            disposable.Using(strategy, func);
        }
 /// <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 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
        }));
    }
        private static void TestStrategyNoException(DisposeExceptionStrategy strategy, bool throwMain, bool throwDispose)
        {
            // No return value:

            var disposable = new SampleDisposable(throwDispose);

            disposable.Using(strategy, _ => RunMainLogic(throwMain));

            Assert.IsTrue(disposable.WasDisposeCalled);

            // With return value:

            disposable = new SampleDisposable(throwDispose);

            var result = disposable.Using(strategy, _ =>
            {
                RunMainLogic(throwMain);
                return(42);
            });

            Assert.IsTrue(disposable.WasDisposeCalled);
            Assert.AreEqual(42, result);
        }
        private static async Task TestStrategyNoExceptionAsync(DisposeExceptionStrategy strategy, bool throwMain, bool throwDispose)
        {
            // No return value:

            var disposable = new SampleDisposable(throwDispose);

            await disposable.UsingAsync(strategy, async _ => await RunMainLogicAsync(throwMain));

            Assert.IsTrue(disposable.WasDisposeCalled);

            // With return value:

            disposable = new SampleDisposable(throwDispose);

            var result = await disposable.UsingAsync(strategy, async _ =>
            {
                await RunMainLogicAsync(throwMain);
                return(42);
            });

            Assert.IsTrue(disposable.WasDisposeCalled);
            Assert.AreEqual(42, result);
        }
Пример #10
0
    /// <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>
    /// 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);
            }
        }
    }
        private static async Task <TResult> UsingAsyncInner <TDisposable, TResult>(this TDisposable disposable, DisposeExceptionStrategy strategy, Func <TDisposable, Task <TResult> > asyncFunc)
            where TDisposable : IDisposable
        {
            Exception mainException = null;

            try
            {
                return(await asyncFunc(disposable).ConfigureAwait(false));
            }
            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>
        /// 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>
        /// <exception cref="ArgumentNullException"><paramref name="disposable"/> or <paramref name="asyncFunc"/> is <see langword="null"/>.</exception>
        /// <remarks>
        /// Refer to the remarks on the <see cref="Using{TDisposable}(TDisposable, DisposeExceptionStrategy, Action{TDisposable})"/> overload.
        /// </remarks>
        public static 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.EnumDefined(strategy, nameof(strategy));

            return(disposable.UsingAsyncInner(strategy, asyncFunc));
        }
        /// <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>
        /// <exception cref="ArgumentNullException"><paramref name="disposable"/> or <paramref name="func"/> is <see langword="null"/>.</exception>
        /// <remarks>
        /// Refer to the remarks on the <see cref="Using{TDisposable}(TDisposable, DisposeExceptionStrategy, Action{TDisposable})"/> overload.
        /// </remarks>
        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.EnumDefined(strategy, nameof(strategy));

            var asyncFunc     = func.WrapAsync();
            var completedTask = disposable.UsingAsync(strategy, asyncFunc);

            return(completedTask.GetResult());   // task is always completed; returns immediately
        }