Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

I have Client which uses a disposable Connection for talking to a remote service

ID: 644489 • Letter: I

Question

I have Client which uses a disposable Connection for talking to a remote service. A Connection is somewhat expensive to set up and needs to be Dispose()d properly.

I want to allow multiple methods in Client to share a connection, like this:

using (client._connection = client.Connect())
{
client.SyncTime();
client.UpdateUsers();
client.SomeOtherOperation();
}
But I don't want to insist on Client's consumers managing connections. They should be allowed to call individual single Client methods without being aware or concerned with Connections.

Here is the pattern I'm using at the moment. It makes SRP cry. Any suggestions on how I can do this better, or should I be doing this at all?

public class Client
{
Connection _connection;

public void SyncTime()
{
    bool wasConnected = _connection == null;
    try
    {
      if (!wasConnected)
        _connection = Connect();

      _connection.ActuallyDoSomeWork();
      ...

    }
    finally
    {
      if (!wasConnected)
        _connection.Dispose()
    }
}
}

Explanation / Answer

You could wrap your disposable Connection in a reference-counted disposable wrapper, like so:

public abstract class RefCountDisposable<TDisposable> : IDisposable where TDisposable : class, IDisposable
{
TDisposable reference;

protected RefCountDisposable(TDisposable reference)
{
this.reference = reference;
}

public TDisposable Reference { get { return reference; } private set { reference = value; } }

public bool IsDisposed { get { return reference == null; } }

public void Dispose()
{
// Dispose of unmanaged resources.
Dispose(true);
// Suppress finalization. Since this class actually has no finalizer, this does nothing.
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (IsDisposed)
return;
if (disposing)
{
// Free any other managed objects here.
Reference = null;
}
// Free any unmanaged objects here.
}

public override string ToString()
{
if (IsDisposed)
return base.ToString() + ": Disposed";
else
return base.ToString() + ": " + reference.ToString();
}
}

public sealed class RefCountDisposableFactory<TDisposable> where TDisposable : class, IDisposable
{
Func<TDisposable> getReference;
TDisposable reference;
int refCount;
object padlock = new object();

sealed class RefCountDisposable : RefCountDisposable<TDisposable>
{
readonly RefCountDisposableFactory<TDisposable> factory;

internal RefCountDisposable(RefCountDisposableFactory<TDisposable> factory, TDisposable reference)
: base(reference)
{
if (factory == null)
throw new ArgumentNullException("factory");
this.factory = factory;
}

protected override void Dispose(bool disposing)
{
if (IsDisposed)
return;
try
{
if (disposing)
{
// Free any other managed objects here.
factory.DisposeReference(Reference);
}
// Free any unmanaged objects here.
}
finally
{
base.Dispose(disposing);
}
}
}

public RefCountDisposableFactory(Func<TDisposable> getReference)
{
this.getReference = getReference;
this.reference = null;
this.refCount = 0;
}

public RefCountDisposable<TDisposable> Create()
{
lock (padlock)
{
if (reference == null)
reference = getReference();
refCount++;
}

return new RefCountDisposable(this, reference);
}

void DisposeReference(TDisposable reference)
{
if (reference == null)
return; // already disposed.
lock (padlock)
{
if (reference != this.reference)
{
string msg = string.Format("invalid reference {0}", reference);
Debug.Assert(false, msg);
throw new ArgumentException(msg);
}
if (refCount <= 1)
{
if (reference != null)
reference.Dispose();
reference = null;
refCount = 0;
}
else
{
refCount--;
}
}
}
}
The idea here is that the RefCountDisposableFactory allocates a Connection the first time you need it and returns it in a RefCountDisposable<Connection> wrapper. Subsequent requests for a Connection increment a reference count and return new wrappers to the same reference Connection. Once the last wrapper is disposed, the Connection is disposed and set to null so that subsequent requests will create a new instance.

My prototype factory isn't robust against somebody disposing the underlying disposable reference directly. Do you think it should be?

I also note Microsoft itself has something called RefCountDisposable, but it isn't generic and I can't find any useful documentation.