1

I use regular Sql* objects to query my database :

// conn is a SqlConnection
// transaction is a SqlTransaction

    using(var cmd = new SqlCommand(someSelectQuery, conn, transaction))
    using(var reader = cmd.ExecuteReader())
    {
        ...
    }

I am to write a wrapper, that would pack the new SqlCommand() and the cmd.ExecuteReader() together :

     using(var someNewReader = GetSelectReader(someSelectQuery, conn, transaction))
     {
        ...
     }

The thing is : this "someNewReader" object (or struct ?) should :

  1. somehow publish the same methods as SqlDataReader
  2. have a .Dispose() method, which disposes of both the underlying SqlCommand and SqlDataReader

What I tried

I tried creating a wrapper class, which holds two fields, a SqlCommand and a SqlDataReader, and :

  1. exposes the methods of the SqlDataReader
  2. reimplements a .Dispose() method which disposes of the two objects (in the correct order)

Re-implementing .Dispose() in a correct maner (especially : handling exceptions the right way, and still try to .Dispose() of everyone) adds a coding overhead which is error prone, and yet would follow the exact smae structure for every .Dispose() chain.

Question

I was wondering if there was a mechanism to "chain together" several IDisposable objects, something that would allow me to describe :

  • input : obj is Disposable
  • input : parent is Disposable
  • output still have an obj (with the same public interface, at least), but which correctly calls obj.Dispose() followed by parent.Dispose() on disposal
LeGEC
  • 46,477
  • 5
  • 57
  • 104
  • 1
    `Re-implementing .Dispose() in a correct maner adds a coding overhead which is error prone` - just let the VS generate the boilerplate for you when you tell it to implement the `IDisposable` on your object, then fill the blanks. Double check your choice against https://stackoverflow.com/a/898867/11683. In your case it's the first option if your class is sealed, or the second without the finalizer if it's not. – GSerg Oct 10 '19 at 22:56
  • As a side note some things do not require you to dispose of them, as if they are passed into an object the object is takes care of disposing them. this commonly occurs when passing streams etc – johnny 5 Oct 10 '19 at 22:58
  • I would make two methods and push the execute reader call to the other (which handles the command and connection) as a delegate. It gives you the method you want but does not 'chain IDisposable' though. – CRice Oct 10 '19 at 23:30

1 Answers1

-1

I don't think there is an easy way to make a wrapper to satify your 1st condition: 1. somehow publish the same methods as SqlDataReader

However, you can return a disposable container and access its items, so that you don't need to implement a wrapper:

class DisposableTupleContainer<D1, D2>: IDisposable
        where D1 : IDisposable
        where D2 : IDisposable
{
    private bool _disposed = false;
    private (D1, D2) _items;
    public D1 Item1 => _disposed ? throw new ObjectDisposedException("d1") : _items.Item1;
    public D2 Item2 => _disposed ? throw new ObjectDisposedException("d2") : _items.Item2;

    public DisposableTupleContainer(D1 d1, Func<D1,D2> d2) 
         // add try catch to destroy d1 if d2() throws and exception
         => _items = (d1, d2(d1)); 

    public void Dispose()
    {
        if (!_disposed)
        {
            // dispose d1, d2, handle exception
            _disposed = true;
        }
    }
}

d2 is a functor so that you can chain construction of disposable objects:

using(var container = new DisposableTupleContainer(
   new SqlCommand(someSelectQuery, conn, transaction), 
   c => c.ExecuteReader())) 
{
  //  container.Item2 is your reader
}

This option gives you strong typing. It is something similar to what you have now. I don't consider it very elegant though :), option with a factory method or just nesting using looks more common to me.

Please note that C# 8 contains some improvements to using, now you can just specify it on declaration:

using var cmd = new SqlCommand(someSelectQuery, conn, transaction);
using var reader = cmd.ExecuteReader();
fenixil
  • 2,106
  • 7
  • 13