Who Disposes of an IDisposable public property?

前端 未结 10 1620
萌比男神i
萌比男神i 2020-12-03 09:59

If I have a SomeDisposableObject class which implements IDisposable:

class SomeDisposableObject : IDisposable
{
    public void Dis         


        
相关标签:
10条回答
  • 2020-12-03 10:37

    I'll try to answer my own question:

    Avoid It in the First Place

    The easiest way out of this situation is to refactor the code to avoid the problem entirely.
    There are two obvious ways to do this.

    External instance creation
    If AContainer does not create a SomeDisposableObject instance, but instead relies on external code to supply it, then AContainer will no longer "own" the instance and is not responsible for disposing of it.

    The externally created instance could be supplied via the constuctor or by setting the property.

    public class AContainerClass
    {
        SomeDisposableObject m_someObject; // No creation here.
    
        public AContainerClass(SomeDisposableObject someObject)
        {
            m_someObject = someObject;
        }
    
        public SomeDisposableObject SomeObject
        {
            get { return m_someObject; }
            set { m_someObject = value; }
        }
    }
    

    Keep the instance private
    The main issue with the posted code is that the ownership is confused. At Dispose time the AContainer class cannot tell who owns the instance. It could be the instance that it created or it could be some other instance that was created externally and set via the property.

    Even if it tracks this and knows for certain that it is dealing with the instance that it created, then it still cannot safely dispose of it as other classes may now have a reference to it that they obtained from the public property.

    If the code can be refactored to avoid making the instance public (i.e. by removing the property entirely) then the issue goes away.

    And If It Can't Be Avoided...

    If for some reason the code cannot be refactored in these ways (as I stipulated in the question) then in my opinion you are left with some fairly difficult design choices.

    Always Dispose of the instance
    If you choose this approach then you are effectively declaring that AContainer will take ownership of the SomeDisposableObject instance when the property is set.

    This makes sense in some situations, particularly where SomeDisposableObject is clearly a transient or subservient object. However it should be documented carefully as it requires the calling code to be aware of this transfer of ownership.

    (It may be more appropriate to use a method, rather than a property, as the method name can be used to give a further hint about ownership).

    public class AContainerClass: IDisposable
    {
        SomeDisposableObject m_someObject = new SomeDisposableObject();
    
        public SomeDisposableObject SomeObject
        {
            get { return m_someObject; }
            set 
            {
                if (m_someObject != null && m_someObject != value)
                    m_someObject.Dispose();
    
                m_someObject = value;
            }
        }
    
        public void Dispose()
        {
            if (m_someObject != null)
                m_someObject.Dispose();
    
            GC.SuppressFinalize(this);
        }
    }
    

    Only Dispose if still the original instance
    In this approach you would track whether the instance was changed from the one originally created by AContainer and only dispose of it when it was the original. Here the ownership model is mixed. AContainer remains the owner of its own SomeDisposableObject instance, but if an external instance is supplied then it remains the responsibility of the external code to dispose of it.

    This approach best reflects the actual situation here, but it can be difficult to implement correctly. The client code can still cause issues by performing operations like this:

    AContainerClass aContainer = new AContainerClass();
    SomeDisposableObject originalInstance = aContainer.SomeObject;
    aContainer.SomeObject = new SomeDisposableObject();
    aContainer.DoSomething();
    aContainer.SomeObject = originalInstance;
    

    Here a new instance was swapped in, a method called, then the original instance was restored. Unfortunately the AContainer will have called Dispose() on the original instance when it was replaced, so it is now invalid.

    Just give Up and let the GC handle it
    This is obviously less than ideal. If the SomeDisposableObject class really does contain some scarce resource then not disposing of it promptly will definitely cause you issues.

    However it may also represent the most robust approach in terms of how the client code interacts with AContainer as it requires no special knowledge of how AContainer treats the ownership of the SomeDisposableObject instance.

    If you know that the disposable resource isn't actually scarce on your system then this may actually be the best approach.


    Some commenters have suggested that it may be possible to use reference counting to track if any other classes still have a reference to the SomeDisposableObject instance. This would be very useful as it would allow us to dispose of it only when we know it is safe to do so and otherwise just let the GC handle it.

    However I am not aware of any C#/.NET API for determining the reference count of an object. If there is one then please let me know.

    0 讨论(0)
  • 2020-12-03 10:38

    You could just flag the Disposal in Dispose(). After all Disposal isn't a destructor - the object still exists.

    so:

    class AContainer : IDisposable
    {
        bool _isDisposed=false;
    
        public void Dispose()
        {
            if (!_isDisposed) 
            {
               // dispose
            }
            _isDisposed=true;
        }
    }
    

    add this to your other class, too.

    0 讨论(0)
  • 2020-12-03 10:48

    The reason you cannot safely call Dispose() on AContainer's instance of SomeDisposableObject is due to lack of encapsulation. The public property provides unrestricted access to part of the internal state. As this part of the internal state must obey the rules of the IDisposable protocol, it is important to make sure that is well encapsulated.

    The problem is similar to allowing access to an instance used for locking. If you do that, it becomes much harder to determine where locks are acquired.

    If you can avoid exposing your disposable instance the problem of who will handle the call to Dispose() goes away as well.

    0 讨论(0)
  • 2020-12-03 10:50

    The real problem might be your object oriented design. If AContainer is Disposed, all its member objects should also be disposed. If not it sound like you can dispose a body but want to keep the leg instance living. Doesn't sound right.

    0 讨论(0)
提交回复
热议问题