这里有几件事要考虑...
首先,您所描述的似乎是Flyweight pattern 的一些变体。您有一个昂贵的对象ServiceClient,您想重用它,但您希望允许消费者对象随意创建和销毁而不破坏昂贵的对象。 Flyweight 传统上通过引用计数来实现这一点,这可能有点过时了。
您需要确保消费者不能直接处置ServiceClient,因此您还需要一个轻量级的Facade,它拦截对ServiceClient.Dispose 的调用并选择是否处置真实对象。您应该对消费者隐藏真实的ServiceClient。
如果一切可行,您可以将您的方法重写为:
// this is the facade that you will work from, instead of ServiceClient
public interface IMyServiceClient : IDisposable
{
void Query(string query);
}
// This is your factory, reworked to provide flyweight instances
// of IMyServiceClient, instead of the real ServiceClient
public class ServiceClientFactory : IDisposable
{
// This is the concrete implementation of IMyServiceClient
// that the factory will create and you can pass around; it
// provides both the reference count and facade implementation
// and is nested inside the factory to indicate that consumers
// should not alter these (and cannot without reflecting on
// non-publics)
private class CachedServiceClient : IMyServiceClient
{
internal ServiceClient _realServiceClient;
internal int _referenceCount;
#region Facade wrapper methods around real ServiceClient ones
void IMyServiceClient.Query(string query)
{
_realServiceClient.Query(query);
}
#endregion
#region IDisposable for the client facade
private bool _isClientDisposed = false;
protected virtual void Dispose(bool disposing)
{
if (!_isClientDisposed)
{
if (Interlocked.Decrement(ref _referenceCount) == 0)
{
// if there are no more references, we really
// dispose the real object
using (_realServiceClient) { /*NOOP*/ }
}
_isClientDisposed = true;
}
}
~CachedServiceClient() { Dispose(false); }
void IDisposable.Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
// The object cache; note that it is not static
private readonly ConcurrentDictionary<string, CachedServiceClient> _cache
= new ConcurrentDictionary<string, CachedServiceClient>();
// The method which allows consumers to create the client; note
// that it returns the facade interface, rather than the concrete
// class, so as to hide the implementation details
public IMyServiceClient CreateServiceClient(string host)
{
var cached = _cache.GetOrAdd(
host,
k => new CachedServiceClient()
);
if (Interlocked.Increment(ref cached._referenceCount) == 1)
{
cached._realServiceClient = new ServiceClient(host);
}
return cached;
}
#region IDisposable for the factory (will forcibly clean up all cached items)
private bool _isFactoryDisposed = false;
protected virtual void Dispose(bool disposing)
{
if (!_isFactoryDisposed)
{
Debug.WriteLine($"ServiceClientFactory #{GetHashCode()} disposing cache");
if (disposing)
{
foreach (var element in _cache)
{
element.Value._referenceCount = 0;
using (element.Value._realServiceClient) { }
}
}
_cache.Clear();
_isFactoryDisposed = true;
}
}
~ServiceClientFactory() { Dispose(false); }
void IDisposable.Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
// This is just an example `ServiceClient` which uses the default
// implementation of GetHashCode to "prove" that new objects aren't
// created unnecessarily; note it does not implement `IMyServiceClient`
public class ServiceClient : IDisposable
{
private readonly string _host;
public ServiceClient(string host)
{
_host = host;
Debug.WriteLine($"ServiceClient #{GetHashCode()} was created for {_host}");
}
public void Query(string query)
{
Debug.WriteLine($"ServiceClient #{GetHashCode()} asked '{query}' to {_host}");
}
public void Dispose()
{
Debug.WriteLine($"ServiceClient #{GetHashCode()} for {_host} was disposed");
GC.SuppressFinalize(this);
}
}
其次,一般来说,我会从工厂中删除static 子句并制作ServiceClientFactory : IDisposable。你会看到我在上面的例子中已经做到了。
您似乎只是将问题推到了链条上,但这样做可以让您根据具体情况做出决定,每个某事(应用程序,会话、请求、单元测试运行——任何有意义的东西)并让代表你的某物的对象负责处理。
如果您的 应用程序 将受益于单实例缓存,则将单例实例公开为 AppContext 类的一部分(例如)并在您的 clean-shutdown 例程中调用 AppContext.DefaultServiceClientFactory.Dispose()。
这里的重点是彻底关闭。正如其他人所说,没有保证您的Dispose 方法实际上会被调用(想想电源循环机器,运行中)。因此,理想情况下,ServiceClient.Dispose 不会有任何有形副作用(即除了释放资源之外,如果进程终止或机器重新启动,自然会释放资源)。
如果ServiceClient.Dispose 确实有明显的副作用,那么您已经确定了这里的风险,并且您应该清楚地记录如何从“不干净”中恢复您的系统" 关机,在随附的用户手册中。
第三,如果ServiceClient 和QueryExecutor 对象都旨在可重用,则让消费者负责创建和处置。
QueryExecutor 在您的示例中只需要是IDisposable,因为它可以拥有ServiceClient(也是IDisposable)。如果 QueryExecutor 没有真正创建 ServiceClient,它不会负责销毁它。
相反,让构造函数采用ServiceClient 参数(或者,使用我的重写,一个IMyServiceClient 参数)而不是string,因此直接使用者可以对所有对象的生命周期负责:
using (var client = AppContext.DefaultServiceClientFactory.CreateServiceClient("localhost"))
{
var query = new QueryExecutor(client);
using (var reader = query.ExecuteReader("SELECT * FROM foo"))
{
//...
}
}
PS:消费者是否需要实际直接访问ServiceClient,或者是否有其他需要引用它的对象?如果没有,也许可以稍微减少链并将这些东西直接移动到QueryExecutor,即使用QueryExecutorFactory 在缓存的ServiceClient 实例周围创建享元QueryExector 对象。