[有一个类似的问题Shared ownership of IDisposable objects in C#但是接受的答案会遇到我的解决方案所存在的类似设计问题]
我一直在努力思考这个问题,因为在c#中工作时,许多设计对垃圾收集器采取了盲目的信念。原因很多时候很容易被忽略,需要处理IDisposable而不是小心地使它们等同于c ++中的资源分配。我理解,迂腐地说Dispose
并不等同于c++ destructor
,但如果你坚持需要以确定性方式清理的本地资源,那就变得非常相似了。
但是如果对象由多个资源共享(例如,一个HttpClient
对象,它意味着要创建并同时用于执行),谁有责任调用Dispose
,因为有没有单身老板?为了解决这个问题,我提出了一个SharedOwner
库,其界面与shared_ptr类似。
代码段:
public static SharedHandle<T> MakeSharedHandle(Func<T> createSharedOwner)
{
var obj = new SharedHandle<T>(createSharedOwner());
return obj;
}
public SharedHandle(T obj)
{
AddReference();
m_object = obj;
}
public Handle<T> GetHandle()
{
AddReference();
return new Handle<T>(this);
}
internal void Decrement()
{
if (Interlocked.Decrement(ref m_refCounter) == 0)
{
m_object.Dispose();
m_object = default(T);
}
}
internal T GetInternalHandler() { return m_object; }
private void AddReference()
{
Interlocked.Increment(ref m_refCounter);
}
~SharedHandle()
{
m_object.Dispose();
}
是一个透明的包装器,它管理引用从消费者那里抽象的引用计数调用。
public sealed class Handle<T> : IDisposable where T : IDisposable
{
private SharedHandle<T> m_handle;
bool disposed = false;
internal Handle(SharedHandle<T> handle) { m_handle = handle; }
public void Dispose()
{
if (!disposed)
{
m_handle.Decrement();
GC.SuppressFinalize(this);
disposed = true;
}
}
~Handle()
{
if (!disposed)
{
m_handle.Decrement();
}
}
我想象的典型使用模式是:
using (var sharedClient = this.m_sharedClient.GetHandle()) // m_sharedClient is the SharedHandle passed
{
var httpClient = (HttpClient)sharedClient;
// use httpClient
}
现在我看到这种方法存在两个问题,这与模拟shared_ptr
行为的原始动机有所不同:
第一个引用由SharedHandle本身持有。因此,即使所有句柄都超出范围,一个引用仍将由SharedHandle保留,从而使得Decrement中的if块无法访问。
最后的Dispose发生在SharedHandle死亡时,在很多意义上并不比不在底层对象本身上调用Dispose更好。因此,使解决方案的价值更低。
我正在考虑将引用计数移动到句柄并在SharedHandle
中使用shared_ptr
作为控制块,但这意味着最终可能会有效{ {1}}对象持有Disposed内部对象。
我能想到的另一个选择是SharedHandle
来自SharedHandle
,只需在IDisposable
中调用Decrement
。但随后这带来了其他一系列设计问题。有没有什么可以以更优雅的方式解决这个问题?