Shared ownership of IDisposable objects in C#

余生颓废 提交于 2019-12-04 21:40:46
Kyle

I think the best you could do is something like this:

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;
}

Basically this allows you to have one object (SharedDisposable<T>) manage the lifetime of the underlying disposable while providing a mechanism to distribute "shared" references to it.

One shortcoming here is that technically anyone could dispose the underlying value by accessing it through the shared reference Value property. You could address this by creating some sort of facade object that wraps the underlying disposable type but hides its Dispose method.

That would a NO. Best way I found so far is quite clunky, using Dictionaries and WeakReferences. The Dictionary maps the object to it's refcount. WeakReference is used so you don't increase the ref count artificially.

You do not own IDisposable, you implement it, so .NET Garbage Collector will call overridden method in your class, notifying about a fact happened.

It's a different concept from shared_ptr, where destructor is guaranteed to be called once last ownership of a pointer is gone.

In general, in .NET, unless you are not using unsafe programming techniques, you do not own anything, .NET Garbage Collector owns it. Even when you explicitly destroy an object, the memory allocated for it may not, and often will not, be reclaimed immediately, like once would expect from C++.

EDIT

If you have native resources and want release them in precise moment, you can achieve that by :

1) Implementing IDisposable with your .NET wrapper object

2) Inside Dispose() method of that wrapper write the code that releases native resources

3) In the code that consumes wrapper object, in the moment you would like to release native resources allocated by wrapper object, call explicitly Dispose() on it.

In this case Dispose() method is called, your code executes and releases native resources immediately.

EDIT (2)

After that is more clear what's the question about:

If you can not determine when Dispose() has to be called, I would stay with @Hans's comment: just relay on eventual (soon or later) GC call and avoid your own reference counter implementation (especially in multi threaded environment). Do not invent the wheel, if that is a feasible in your situation.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!