C# 中 IDisposable 对象的共享所有权
Shared ownership of IDisposable objects in C#
C# 中是否有 类 提供 IDisposable 对象的共享所有权?像 C++ 中的 shared_ptr 这样的东西?如果没有,这里的最佳做法是什么?
更新
我正在本机库上编写一个 c++/cli 包装器。我需要释放本机资源(例如MAPI COM 接口,所以我需要确定性资源释放)。
原生部分:
//Message.h
class Message
{ ... };
//MessageSet.h
class MessageSet
{
...
class iterator : public std::iterator<std::forward_iterator_tag, Message*>
{
...
public:
Message* operator*();
bool operator!=(const iterator& that);
iterator& operator++();
};
iterator begin();
iterator end();
};
托管部分 (c++/cli):
public ref class Message
{
native::Message* inst;
public:
Message(native::Message* inst);
~Message();
!Message();
};
public ref class MessageSet : public IEnumerable<Message^>
{
native::MessageSet* inst;
public:
MessageSet(native::Message* inst);
~MessageSet();
!MessageSet();
virtual IEnumerator<Message^>^ GetEnumerator();
virtual System::Collections::IEnumerator^ EnumerableGetEnumerator() = System::Collections::IEnumerable::GetEnumerator;
};
当我在 C# 中的 TPL 数据流(BroadcastBlock 块,即有许多并发消费者)中使用 Message 对象时,我不知道何时应该为这些消息调用 Dispose()。
那就不行了。
到目前为止,我发现的最佳方法非常笨拙,使用字典和 WeakReferences。字典将对象映射到它的引用计数。使用 WeakReference 所以你不会人为地增加引用计数。
您不拥有 IDisposable
,您实现它,因此.NET Garbage Collector将在你的 class,通知一个事实发生了。
这是与 shared_ptr 不同的概念,后者 保证 在指针的最后所有权消失后调用析构函数。
一般来说,在 .NET 中,除非您不使用 不安全 编程技术,否则您不会 拥有 任何东西,.NET Garbage Collector 拥有它。
即使当您显式销毁一个对象时,为它分配的内存也可能不会,而且通常不会立即被回收,就像 C++
.
曾经期望的那样
编辑
如果您有本机资源并希望在精确的时刻释放它们,您可以通过以下方式实现:
1) 使用 .NET 包装对象
实现 IDisposable
2) 在包装器的 Dispose()
方法中编写释放 native resources
的代码
3) 在消费wrapper对象的代码中,当您想释放wrapper对象分配的native资源时,显式调用Dispose()
即可。
在这种情况下调用 Dispose()
方法,您的代码将立即执行并释放 本机资源 。
编辑 (2)
之后更清楚问题是什么:
如果您无法确定何时必须调用 Dispose()
,我会保留@Hans 的评论:仅在最终(迟早)调用 GC
并避免使用您自己的参考计数器实现(特别是在多线程环境中)。
如果在您的情况下可行,请不要发明轮子。
我认为你能做的最好的事情是这样的:
public sealed class SharedDisposable<T> where T : IDisposable
{
public sealed class Reference : IDisposable
{
public Reference( SharedDisposable<T> owner )
{
mOwner = owner;
}
public void Dispose()
{
if( mIsDisposed ) return;
mIsDisposed = true;
mOwner.Release();
}
public T Value => mOwner.mValue;
private readonly SharedDisposable<T> mOwner;
private bool mIsDisposed;
}
public SharedDisposable( T value )
{
mValue = value;
}
public Reference Acquire()
{
lock( mLock )
{
if( mRefCount < 0 ) throw new ObjectDisposedException( typeof( T ).FullName );
mRefCount++;
return new Reference( this );
}
}
private void Release()
{
lock( mLock )
{
mRefCount--;
if( mRefCount <= 0 )
{
mValue.Dispose();
mRefCount = -1;
}
}
}
private readonly T mValue;
private readonly object mLock = new object();
private int mRefCount;
}
基本上,这允许您让一个对象 (SharedDisposable<T>
) 管理基础一次性对象的生命周期,同时提供一种机制来分发对它的 "shared" 引用。
这里的一个缺点是,从技术上讲,任何人都可以通过共享引用 Value
属性 访问它来处理基础值。您可以通过创建某种包装底层一次性类型但隐藏其 Dispose
方法的外观对象来解决此问题。
C# 中是否有 类 提供 IDisposable 对象的共享所有权?像 C++ 中的 shared_ptr 这样的东西?如果没有,这里的最佳做法是什么?
更新
我正在本机库上编写一个 c++/cli 包装器。我需要释放本机资源(例如MAPI COM 接口,所以我需要确定性资源释放)。
原生部分:
//Message.h
class Message
{ ... };
//MessageSet.h
class MessageSet
{
...
class iterator : public std::iterator<std::forward_iterator_tag, Message*>
{
...
public:
Message* operator*();
bool operator!=(const iterator& that);
iterator& operator++();
};
iterator begin();
iterator end();
};
托管部分 (c++/cli):
public ref class Message
{
native::Message* inst;
public:
Message(native::Message* inst);
~Message();
!Message();
};
public ref class MessageSet : public IEnumerable<Message^>
{
native::MessageSet* inst;
public:
MessageSet(native::Message* inst);
~MessageSet();
!MessageSet();
virtual IEnumerator<Message^>^ GetEnumerator();
virtual System::Collections::IEnumerator^ EnumerableGetEnumerator() = System::Collections::IEnumerable::GetEnumerator;
};
当我在 C# 中的 TPL 数据流(BroadcastBlock 块,即有许多并发消费者)中使用 Message 对象时,我不知道何时应该为这些消息调用 Dispose()。
那就不行了。 到目前为止,我发现的最佳方法非常笨拙,使用字典和 WeakReferences。字典将对象映射到它的引用计数。使用 WeakReference 所以你不会人为地增加引用计数。
您不拥有 IDisposable
,您实现它,因此.NET Garbage Collector将在你的 class,通知一个事实发生了。
这是与 shared_ptr 不同的概念,后者 保证 在指针的最后所有权消失后调用析构函数。
一般来说,在 .NET 中,除非您不使用 不安全 编程技术,否则您不会 拥有 任何东西,.NET Garbage Collector 拥有它。
即使当您显式销毁一个对象时,为它分配的内存也可能不会,而且通常不会立即被回收,就像 C++
.
编辑
如果您有本机资源并希望在精确的时刻释放它们,您可以通过以下方式实现:
1) 使用 .NET 包装对象
实现IDisposable
2) 在包装器的 Dispose()
方法中编写释放 native resources
3) 在消费wrapper对象的代码中,当您想释放wrapper对象分配的native资源时,显式调用Dispose()
即可。
在这种情况下调用 Dispose()
方法,您的代码将立即执行并释放 本机资源 。
编辑 (2)
之后更清楚问题是什么:
如果您无法确定何时必须调用 Dispose()
,我会保留@Hans 的评论:仅在最终(迟早)调用 GC
并避免使用您自己的参考计数器实现(特别是在多线程环境中)。
如果在您的情况下可行,请不要发明轮子。
我认为你能做的最好的事情是这样的:
public sealed class SharedDisposable<T> where T : IDisposable
{
public sealed class Reference : IDisposable
{
public Reference( SharedDisposable<T> owner )
{
mOwner = owner;
}
public void Dispose()
{
if( mIsDisposed ) return;
mIsDisposed = true;
mOwner.Release();
}
public T Value => mOwner.mValue;
private readonly SharedDisposable<T> mOwner;
private bool mIsDisposed;
}
public SharedDisposable( T value )
{
mValue = value;
}
public Reference Acquire()
{
lock( mLock )
{
if( mRefCount < 0 ) throw new ObjectDisposedException( typeof( T ).FullName );
mRefCount++;
return new Reference( this );
}
}
private void Release()
{
lock( mLock )
{
mRefCount--;
if( mRefCount <= 0 )
{
mValue.Dispose();
mRefCount = -1;
}
}
}
private readonly T mValue;
private readonly object mLock = new object();
private int mRefCount;
}
基本上,这允许您让一个对象 (SharedDisposable<T>
) 管理基础一次性对象的生命周期,同时提供一种机制来分发对它的 "shared" 引用。
这里的一个缺点是,从技术上讲,任何人都可以通过共享引用 Value
属性 访问它来处理基础值。您可以通过创建某种包装底层一次性类型但隐藏其 Dispose
方法的外观对象来解决此问题。