Saturday, August 8, 2009

Making IDisposable more usable, part 1

IDisposable is used quite frequently, so here I’m going to provide few hacks allowing to improve its usability. First of all, let's create IDisposableExtensions with a single method:

/// <summary>
/// Safely disposes an <see cref="IDisposable"/> object.
/// </summary>
/// <param name="disposable">Object to dispose (can be <see langword="null"/>).</param>
public static void DisposeSafely(this IDisposable disposable)
{
if (disposable!=null)
disposable.Dispose();
}

This gives us an ability to dispose the objects without repeating null-check:

try {
writer.DisposeSafely();
}
finally {
writer = null;
}

Let's go further now. Frequently you must safely dispose two or more IDisposable objects. Note that "safely" is an essential word here. You can't write something like:

disposable1.DisposeSafely();
disposable2.DisposeSafely();

This code may fail: an exception can be thrown on execution of its first line, and if this happens, its second line won't be executed at all. So disposable2 has a chance of not being disposed. That's why it's a good idea to implement few helpers allowing us to deal with such issues safely.

Let's add JoiningDisposable type:

/// <summary>
/// Disposes two <see cref="IDisposable"/> objects.
/// </summary>
[Serializable]
public sealed class JoiningDisposable : IDisposable
{
private IDisposable first;
private IDisposable second;

/// <summary>
/// Gets the first object to dispose.
/// </summary>
public IDisposable First {
get { return first; }
}

/// <summary>
/// Gets the second object to dispose.
/// </summary>
public IDisposable Second {
get { return second; }
}

/// <summary>
/// Joins the <see cref="JoiningDisposable"/> and <see cref="IDisposable"/>.
/// </summary>
/// <param name="first">The first disposable to join.</param>
/// <param name="second">The second disposable to join.</param>
/// <returns>New <see cref="JoiningDisposable"/> that will
/// dispose both of them on its disposal</returns>
public static JoiningDisposable operator &(JoiningDisposable first, IDisposable second)
{
if (second==null)
return first;
return new JoiningDisposable(first, second);
}


// Constructors

/// <summary>
///   <see cref="ClassDocTemplate.Ctor" copy="true"/>
/// </summary>
/// <param name="disposable1">The first disposable.</param>
/// <param name="disposable2">The second disposable.</param>
public JoiningDisposable(IDisposable disposable1, IDisposable disposable2)
{
this.first = disposable1;
this.second = disposable2;
}

/// <inheritdoc/>
public void Dispose()
{
var d1 = first;
first = null;
try {
d1.DisposeSafely();
}
catch (Exception ex) {
using (var ea = new ExceptionAggregator()) {
ea.Execute(_this => {
var d2 = _this.second;
_this.second = null;
d2.DisposeSafely();
}, this);
ea.Execute(e => { throw e; }, ex);
}
}
d1 = second;
second = null;
d1.DisposeSafely();
}
}

As you see, the code in its Dispose method relies on ExceptionAggregator. Here it is:

/// <summary>
/// Provides exception aggregation support.
/// </summary>
[Serializable]
public class ExceptionAggregator : 
IDisposable, 
ICountable<Exception>
{
private Action<Exception> exceptionHandler;
private List<Exception> exceptions;
private string exceptionMessage;

private bool isDisposed = false;

/// <summary>
/// Gets or sets the exception handler.
/// </summary>
public Action<Exception> ExceptionHandler
{
[DebuggerStepThrough]
get { return exceptionHandler; }
[DebuggerStepThrough]
set { exceptionHandler = value; }
}

/// <summary>
/// Gets the number of caught exceptions.
/// </summary>
public long Count
{
[DebuggerStepThrough]
get { return exceptions!=null ? exceptions.Count : 0; }
}

#region Execute(...) methods

/// <summary>
/// Executes the specified action catching all the exceptions from it,
/// adding it to internal list of caught exceptions and
/// and passing it to <see cref="ExceptionHandler"/> handler.
/// </summary>
/// <param name="action">The action to execute.</param>
/// <exception cref="ObjectDisposedException">Aggregator is already disposed.</exception>
public void Execute(Action action)
{
if (isDisposed)
throw Exceptions.AlreadyDisposed(null);
try {
action();
}
catch (Exception e) {
HandleException(e);
}
}

/// <summary>
/// Executes the specified action catching all the exceptions from it,
/// adding it to internal list of caught exceptions and
/// and passing it to <see cref="ExceptionHandler"/> handler.
/// </summary>
/// <typeparam name="T">The type of action argument.</typeparam>
/// <param name="action">The action to execute.</param>
/// <param name="argument">The action argument value.</param>
/// <exception cref="ObjectDisposedException">Aggregator is already disposed.</exception>
public void Execute<T>(Action<T> action, T argument)
{
if (isDisposed)
throw Exceptions.AlreadyDisposed(null);
try {
action(argument);
}
catch (Exception e) {
HandleException(e);
}
}

/// <summary>
/// Executes the specified action catching all the exceptions from it,
/// adding it to internal list of caught exceptions and
/// and passing it to <see cref="ExceptionHandler"/> handler.
/// </summary>
/// <typeparam name="T1">The type of the 1st action argument.</typeparam>
/// <typeparam name="T2">The type of the 2nd action argument.</typeparam>
/// <param name="action">The action to execute.</param>
/// <param name="argument1">The 1st action argument value.</param>
/// <param name="argument2">The 2nd action argument value.</param>
/// <exception cref="ObjectDisposedException">Aggregator is already disposed.</exception>
public void Execute<T1, T2>(Action<T1, T2> action, T1 argument1, T2 argument2)
{
if (isDisposed)
throw Exceptions.AlreadyDisposed(null);
try {
action(argument1, argument2);
}
catch (Exception e) {
HandleException(e);
}
}

/// <summary>
/// Executes the specified action catching all the exceptions from it,
/// adding it to internal list of caught exceptions and
/// and passing it to <see cref="ExceptionHandler"/> handler.
/// </summary>
/// <typeparam name="T1">The type of the 1st action argument.</typeparam>
/// <typeparam name="T2">The type of the 2nd action argument.</typeparam>
/// <typeparam name="T3">The type of the 3rd action argument.</typeparam>
/// <param name="action">The action to execute.</param>
/// <param name="argument1">The 1st action argument value.</param>
/// <param name="argument2">The 2nd action argument value.</param>
/// <param name="argument3">The 3rd action argument value.</param>
/// <exception cref="ObjectDisposedException">Aggregator is already disposed.</exception>
public void Execute<T1, T2, T3>(Action<T1, T2, T3> action, T1 argument1, T2 argument2, T3 argument3)
{
if (isDisposed)
throw Exceptions.AlreadyDisposed(null);
try {
action(argument1, argument2, argument3);
}
catch (Exception e) {
HandleException(e);
}
}

/// <summary>
/// Executes the specified function catching all the exceptions from it,
/// adding it to internal list of caught exceptions and
/// and passing it to <see cref="ExceptionHandler"/> handler.
/// </summary>
/// <typeparam name="TResult">The type of function result.</typeparam>
/// <param name="function">The function to execute.</param>
/// <returns>Function execution result, if no exception was caught;
/// otherwise, <see langword="default(TResult)"/>.</returns>
/// <exception cref="ObjectDisposedException">Aggregator is already disposed.</exception>
public TResult Execute<TResult>(Func<TResult> function)
{
if (isDisposed)
throw Exceptions.AlreadyDisposed(null);
try {
return function();
}
catch (Exception e) {
HandleException(e);
}
return default(TResult);
}

/// <summary>
/// Executes the specified function catching all the exceptions from it,
/// adding it to internal list of caught exceptions and
/// and passing it to <see cref="ExceptionHandler"/> handler.
/// </summary>
/// <typeparam name="T">The type of the function argument.</typeparam>
/// <typeparam name="TResult">The type of function result.</typeparam>
/// <param name="function">The function to execute.</param>
/// <param name="argument">The function argument value.</param>
/// <returns>Function execution result, if no exception was caught;
/// otherwise, <see langword="default(TResult)"/>.</returns>
/// <exception cref="ObjectDisposedException">Aggregator is already disposed.</exception>
public TResult Execute<T, TResult>(Func<T, TResult> function, T argument)
{
if (isDisposed)
throw Exceptions.AlreadyDisposed(null);
try {
return function(argument);
}
catch (Exception e) {
HandleException(e);
}
return default(TResult);
}

/// <summary>
/// Executes the specified function catching all the exceptions from it,
/// adding it to internal list of caught exceptions and
/// and passing it to <see cref="ExceptionHandler"/> handler.
/// </summary>
/// <typeparam name="T1">The type of the 1st function argument.</typeparam>
/// <typeparam name="T2">The type of the 2nd function argument.</typeparam>
/// <typeparam name="TResult">The type of function result.</typeparam>
/// <param name="function">The function to execute.</param>
/// <param name="argument1">The 1st function argument value.</param>
/// <param name="argument2">The 2nd function argument value.</param>
/// <returns>Function execution result, if no exception was caught;
/// otherwise, <see langword="default(TResult)"/>.</returns>
/// <exception cref="ObjectDisposedException">Aggregator is already disposed.</exception>
public TResult Execute<T1, T2, TResult>(Func<T1, T2, TResult> function, T1 argument1, T2 argument2)
{
if (isDisposed)
throw Exceptions.AlreadyDisposed(null);
try {
return function(argument1, argument2);
}
catch (Exception e) {
HandleException(e);
}
return default(TResult);
}

/// <summary>
/// Executes the specified function catching all the exceptions from it,
/// adding it to internal list of caught exceptions and
/// and passing it to <see cref="ExceptionHandler"/> handler.
/// </summary>
/// <typeparam name="T1">The type of the 1st function argument.</typeparam>
/// <typeparam name="T2">The type of the 2nd function argument.</typeparam>
/// <typeparam name="T3">The type of the 3rd function argument.</typeparam>
/// <typeparam name="TResult">The type of function result.</typeparam>
/// <param name="function">The function to execute.</param>
/// <param name="argument1">The 1st function argument value.</param>
/// <param name="argument2">The 2nd function argument value.</param>
/// <param name="argument3">The 3rd function argument value.</param>
/// <returns>Function execution result, if no exception was caught;
/// otherwise, <see langword="default(TResult)"/>.</returns>
/// <exception cref="ObjectDisposedException">Aggregator is already disposed.</exception>
public TResult Execute<T1, T2, T3, TResult>(Func<T1, T2, T3, TResult> function, T1 argument1, T2 argument2, T3 argument3)
{
if (isDisposed)
throw Exceptions.AlreadyDisposed(null);
try {
return function(argument1, argument2, argument3);
}
catch (Exception e) {
HandleException(e);
}
return default(TResult);
}

#endregion

#region IEnumerable<...> methods

/// <inheritdoc/>
[DebuggerStepThrough]
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

/// <inheritdoc/>
public IEnumerator<Exception> GetEnumerator()
{
if (exceptions==null)
return EnumerableUtils<Exception>.EmptyEnumerator;
else 
return exceptions.GetEnumerator();
}

#endregion

/// <summary>
/// Invoked on any exception caught by <see cref="Execute"/> methods.
/// </summary>
/// <param name="exception">The caught exception.</param>
/// <remarks>
/// If this method throws an exception, it won't be caught.
/// I.e. it will throw "through" any of <see cref="Execute"/> methods.
/// </remarks>
protected virtual void HandleException(Exception exception)
{
if (exceptionHandler!=null)
exceptionHandler(exception);
if (exceptions==null)
exceptions = new List<Exception>();
exceptions.Add(exception);
}


// Constructors

/// <summary>
/// <see cref="ClassDocTemplate.Ctor" copy="true"/>
/// </summary>
public ExceptionAggregator()
: this(null, null)
{
}

/// <summary>
/// <see cref="ClassDocTemplate.Ctor" copy="true"/>
/// </summary>
/// <param name="exceptionMessage">The message of <see cref="AggregateException"/>.</param>
public ExceptionAggregator(string exceptionMessage)
: this (null, exceptionMessage)
{
}

/// <summary>
/// <see cref="ClassDocTemplate.Ctor" copy="true"/>
/// </summary>
/// <param name="exceptionHandler">The exception handler.</param>
/// <param name="exceptionMessage">The message of <see cref="AggregateException"/>.</param>
public ExceptionAggregator(Action<Exception> exceptionHandler, string exceptionMessage)
{
this.exceptionHandler = exceptionHandler;
this.exceptionMessage = exceptionMessage;
}

// Descructor

/// <see cref="ClassDocTemplate.Dispose" copy="true"/>
/// <exception cref="AggregateException">Thrown if at least one exception was caught 
/// by <see cref="Execute"/> methods.</exception>
public void Dispose()
{
if (exceptions!=null && exceptions.Count>0) {
Exception exception = string.IsNullOrEmpty(exceptionMessage) ? 
new AggregateException(exceptions) : 
new AggregateException(exceptionMessage, exceptions);        
exceptions = null;
isDisposed = true;
throw exception;
}
}
}

Finally, both these types use AggregateException:

/// <summary>
/// Aggregates a set of caught exceptions.
/// </summary>
[Serializable]
public class AggregateException : Exception,
IHasExceptions<Exception>
{
private ReadOnlyList<Exception> exceptions;

/// <summary>
/// Gets the list of caught exceptions.
/// </summary>
public ReadOnlyList<Exception> Exceptions
{
[DebuggerStepThrough]
get { return exceptions; }
}

/// <inheritdoc/>
IEnumerable<Exception> IHasExceptions.Exceptions
{
[DebuggerStepThrough]
get { return Exceptions; }
}

/// <inheritdoc/>
IEnumerable<Exception> IHasExceptions<Exception>.Exceptions
{
[DebuggerStepThrough]
get { return Exceptions; }
}

/// <summary>
/// Gets the "flat" list with all aggregated exceptions. 
/// If other <see cref=" AggregateException"/>s were aggregated, 
/// their inner exceptions are included instead of them.
/// </summary>
/// <returns>Flat list of aggregated exceptions.</returns>
public List<Exception> GetFlatExceptions()
{
var result = new List<Exception>();

foreach (var exception in exceptions) {
var ae = exception as AggregateException;
if (ae!=null)
result.AddRange(ae.GetFlatExceptions());
else
result.Add(exception);
}

return result;
}

/// <inheritdoc/>
public override string ToString()
{
StringBuilder sb = new StringBuilder(64);
sb.Append(base.ToString());
sb.AppendFormat("\r\n{0}:", Strings.OriginalExceptions);
int i = 1;
foreach (Exception exception in exceptions)
sb.AppendFormat("\r\n{0}: {1}", i++, exception);
return sb.ToString();
}

#region Private \ internal methods

private void SetExceptions(IEnumerable<Exception> exceptions)
{
var list = exceptions as IList<Exception> ?? exceptions.ToList();
this.exceptions = new ReadOnlyList<Exception>(list);
}

private void SetExceptions(Exception exception)
{
var list = new List<Exception>();
list.Add(exception);
exceptions = new ReadOnlyList<Exception>(list);
}

#endregion


// Constructors

/// <summary>
/// <see cref="ClassDocTemplate.Ctor" copy="true" />
/// </summary>
public AggregateException()
: base(Strings.ExASetOfExceptionsIsCaught)
{
}

/// <summary>
/// <see cref="ClassDocTemplate.Ctor" copy="true" />
/// </summary>
/// <param name="text">Text of message.</param>
public AggregateException(string text)
: base(text)
{
}

/// <summary>
/// <see cref="ClassDocTemplate.Ctor" copy="true" />
/// </summary>
/// <param name="message">Text of message.</param>
/// <param name="innerException">Inner exception.</param>
public AggregateException(string message, Exception innerException) 
: base(message, innerException)
{
SetExceptions(innerException);
}

/// <summary>
/// <see cref="ClassDocTemplate.Ctor" copy="true" />
/// </summary>
/// <param name="exceptions">Inner exceptions.</param>
public AggregateException(IEnumerable<Exception> exceptions) 
: base(Strings.ExASetOfExceptionsIsCaught, exceptions.First())
{
SetExceptions(exceptions);
}

/// <summary>
/// <see cref="ClassDocTemplate.Ctor" copy="true" />
/// </summary>
/// <param name="message">Text of message.</param>
/// <param name="exceptions">Inner exceptions.</param>
public AggregateException(string message, IEnumerable<Exception> exceptions) 
: base(message, exceptions.First())
{
SetExceptions(exceptions);
}


// Serialization

/// <see cref="SerializableDocTemplate.Ctor" copy="true" />
protected AggregateException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
exceptions = (ReadOnlyList<Exception>)info.GetValue("Exceptions", typeof (ReadOnlyList<Exception>));
}

/// <see cref="SerializableDocTemplate.GetObjectData" copy="true" />
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("Exceptions", exceptions);
}
}

And now we're going to the final step. Let's add one more extension method to our DisposableExtensions:

/// <summary>
/// Joins the specified disposable objects by returning
/// a single <see cref="JoiningDisposable"/> that will
/// dispose both of them on its disposal.
/// </summary>
/// <param name="disposable">The first disposable.</param>
/// <param name="joinWith">The second disposable.</param>
/// <returns>New <see cref="JoiningDisposable"/> that will
/// dispose both of them on its disposal</returns>
public static JoiningDisposable Join(this IDisposable disposable, IDisposable joinWith)
{
return new JoiningDisposable(disposable, joinWith);
}

When this is done, you can use the following code with Join to safely dispose two or more IDisposables:

/// <inheritdoc/>
[DebuggerStepThrough]
public override object OnEntry(object instance)
{
// ...
var sessionScope = sessionBound.ActivateContext();
var transactionScope = Transaction.Open(sessionBound.Session, true)
return transactionScope.Join(sessionScope); // Joins 2 disposables into one
}

// ...

/// <inheritdoc/>
[DebuggerStepThrough]
public override void OnExit(object instance, object onEntryResult)
{
var d = (IDisposable) onEntryResult;
d.DisposeSafely(); // Safely disposes 2 disposables
}  

P.S. All the code provided here was taken from our Xtensive.Core assembly. There are JoiningDisposable, DisposableExtensions and everything else I just described.

Next time I'll tell you how to simplify safe dealing with IDisposable further.

31 comments:

  1. maybe it's a stupid suggestion but why don't you use the 'using ***' pattern?

    if you use a resource with
    using (var x = XXX)
    {
    }

    you don't have problem about null checks, also about using more objects i think that the 'using' is more clear that your solution

    using (var x1 = new x1())
    using (var x2 = new x2())
    {

    }
    i don't remember but maybe works also

    using (var x1 = new x1(),
    var x2 = new x2())
    {
    }

    ReplyDelete
  2. "using" statement is limited to the same block of code. But in real life you frequently create IDisposable objects in constructors and dispose them in your own IDisposable.Dispose - so these patterns are designed mainly for this case.

    ReplyDelete
  3. Interesting stuff, thx.

    About aggregated exception. Exception handling just cannot be done right. Otherwise it's not exception handling, it's control flow management. I'ts better to avoid exception features under this angle of view. I suppose something is wrong with exception aggregation...

    ReplyDelete
  4. Nearly the same approach is used in Parallel Extensions \ PLINQ - it also may throw AggregatedException in case of multiple concurrent errors. So I suspect that's the best option we had.

    Btw, exception handling in general is really a bit ugly way. Check out how this is implemented in pure functional languages like Haskell.

    ReplyDelete
  5. >>Nearly the same approach is used in Parallel Extensions \ PLINQ - it also may throw AggregatedException in case of multiple concurrent errors. So I suspect that's the best option we had.

    I suppose it's the only considerable option PLINQ have.

    Aggregated exceptions causes separating catches.
    I think exception handling is something like tolerance level definition. Also exception handling by design can be used to fight unsuitable API of 3rd party products.

    Btw, Thx God I know imperative programming, hence I don't like Haskell.

    ReplyDelete
  6. Errrmm... "hence" is not exact word.
    it's evoked by agressive functional programming funs, epic vsl followers.

    ReplyDelete
  7. Concerning Haskell: well, there are lots of practically good ideas:
    - LINQ came from it (monads)
    - Parallelism is absolutely free in pure functional languages.
    - It syntax is also very nice: short & clean.

    So in fact, C# and F# took a lot from functional languages. But agree, currently usage of imperative style seems inevitable (= practically efficient).

    ReplyDelete
  8. That appears to be excellent however i am still not too sure that I like it. At any rate will look far more into it and decide personally! cb-world.ru

    ReplyDelete
  9. Before starting the fight, choose among 3 robots with different Speed, Power and Defense stats. download music You are no need to afraid losing yourself in Taipei City anymore.

    ReplyDelete
  10. Create wish lists of products you see in stores and publish them to the High-End Shopper community. downlodable music This app serves as a reference for every SNES game ever released, including unlicensed ones.

    ReplyDelete
  11. The ability to use facebook connect to login and see your friend&'s submitted and favorite riddles. downloadnowfreeware.com FEEDBACK: Contact us with your ideas, suggestions or feedback.

    ReplyDelete
  12. So much we find the benefits of reading this article. hammer of thor

    ReplyDelete
  13. Emailed dot software and explained my issues and that I wanted instructions for a refund. downlodable tv shows Before uploading files to Usenet or WWW you may encrypt them and hide inside one another.

    ReplyDelete
  14. Supported document types viewing on iPhone are Microsoft Word, PowerPoint and Excel, Adobe Acrobat, text and more. downlodable torrent Sometimes this means the current version has issues, but we work them out fast.

    ReplyDelete
  15. You dont have to be a bookkeeper to understand and use it. downlodable music - Mark your favorite recipes, and them in the Favorites area.

    ReplyDelete
  16. I personally like your post, you have shared good article. https://www.case-study-solutions.com It will help me in great deal.

    ReplyDelete
  17. Really i appreciate the effort you made to share the knowledge. http://www.domyprogramminghomework.net/

    ReplyDelete
  18. This version supports iOS7 and includes new resort listings for 2014. manual de instalaciones hidraulicas becerril pdf For thousands of years, people threw sticks or coins to access its wisdom.

    ReplyDelete
  19. BEAT YOUR FRIENDS! Throw down in Word Smack the addictive 2-player word guessing game. 2305 bir form pdf Most of the drop-down lists now can be refreshed from the database.

    ReplyDelete
  20. Make learning fun and exciting with the Preschool Fun pack. downlodable freeware NY Times Reuters Topix BBC News USA Today Times Mobile ABC News Fox News Digg Time AP News CNN MSNBC CBC The Washington Post.

    ReplyDelete
  21. From Mingxin Le: The unique livebook which everything in it is interactive. http://awesomedownloadfilesdatabase.us Tips are SHORT, concise and to the point - Stroke of Genius" 5 STARS"

    ReplyDelete
  22. Between 1921 and 1939, she wrote one Oz book a year. http://bestdownloadwarezarchive.us The picture link is ready to be posted everywhere on the web if you want (step 4 publishing).

    ReplyDelete