ci sono diverse strategie per la moltiplicazione o deglutire eccezioni al metodo di Dispose
, possibilmente in base al fatto un'eccezione unhanded è stato anche gettato dalla logica principale. La soluzione migliore sarebbe lasciare la decisione al chiamante, in base alle proprie esigenze specifiche.Ho implementato un metodo di estensione generica che fa questo, che offre:
- il default
using
semantica di moltiplicazione Dispose
eccezioni
- Marc Gravell's suggestion di deglutizione sempre
Dispose
eccezioni
- maxyfc's alternative di soli deglutizione
Dispose
eccezioni quando c'è un'eccezione dalla logica principale che altrimenti andrebbe persa
- Daniel Chambers's approach di avvolgere più eccezioni in un
AggregateException
- un approccio simile che avvolge sempre tutte le eccezioni in un
AggregateException
(come Task.Wait
fa)
Questo è il mio metodo di estensione:
/// <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);
}
}
}
Queste sono le strategie messe in atto:
/// <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,
}
Esempio di utilizzo:
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
Aggiornamento: Se è necessario supportare i delegati che restituiscono valori e/o sono asincrone, allora si potrebbe utilizzare questi sovraccarichi:
/// <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);
}
}
}
per il contesto: WCF è famoso per questo ... –
Molto buona domanda . Grazie. Comunque penso che la risposta di Richard sia migliore a causa della separazione di Close e Dispose. –
Grazie per aver chiesto questo! Esattamente la situazione in cui mi sono appena accorto, dopo aver seguito uno stack di chiamate molto ottuso: P – shambulator