Proper use of the IDisposable interface

后端 未结 19 3329
情深已故
情深已故 2020-11-21 04:05

I know from reading the Microsoft documentation that the \"primary\" use of the IDisposable interface is to clean up unmanaged resources.

To me, \"unman

相关标签:
19条回答
  • 2020-11-21 04:50

    First of definition. For me unmanaged resource means some class, which implements IDisposable interface or something created with usage of calls to dll. GC doesn't know how to deal with such objects. If class has for example only value types, then I don't consider this class as class with unmanaged resources. For my code I follow next practices:

    1. If created by me class uses some unmanaged resources then it means that I should also implement IDisposable interface in order to clean memory.
    2. Clean objects as soon as I finished usage of it.
    3. In my dispose method I iterate over all IDisposable members of class and call Dispose.
    4. In my Dispose method call GC.SuppressFinalize(this) in order to notify garbage collector that my object was already cleaned up. I do it because calling of GC is expensive operation.
    5. As additional precaution I try to make possible calling of Dispose() multiple times.
    6. Sometime I add private member _disposed and check in method calls did object was cleaned up. And if it was cleaned up then generate ObjectDisposedException
      Following template demonstrates what I described in words as sample of code:

    public class SomeClass : IDisposable
        {
            /// <summary>
            /// As usually I don't care was object disposed or not
            /// </summary>
            public void SomeMethod()
            {
                if (_disposed)
                    throw new ObjectDisposedException("SomeClass instance been disposed");
            }
    
            public void Dispose()
            {
                Dispose(true);
            }
    
            private bool _disposed;
    
            protected virtual void Dispose(bool disposing)
            {
                if (_disposed)
                    return;
                if (disposing)//we are in the first call
                {
                }
                _disposed = true;
            }
        }
    
    0 讨论(0)
  • 2020-11-21 04:51

    Your given code sample is not a good example for IDisposable usage. Dictionary clearing normally shouldn't go to the Dispose method. Dictionary items will be cleared and disposed when it goes out of scope. IDisposable implementation is required to free some memory/handlers that will not release/free even after they out of scope.

    The following example shows a good example for IDisposable pattern with some code and comments.

    public class DisposeExample
    {
        // A base class that implements IDisposable. 
        // By implementing IDisposable, you are announcing that 
        // instances of this type allocate scarce resources. 
        public class MyResource: IDisposable
        {
            // Pointer to an external unmanaged resource. 
            private IntPtr handle;
            // Other managed resource this class uses. 
            private Component component = new Component();
            // Track whether Dispose has been called. 
            private bool disposed = false;
    
            // The class constructor. 
            public MyResource(IntPtr handle)
            {
                this.handle = handle;
            }
    
            // Implement IDisposable. 
            // Do not make this method virtual. 
            // A derived class should not be able to override this method. 
            public void Dispose()
            {
                Dispose(true);
                // This object will be cleaned up by the Dispose method. 
                // Therefore, you should call GC.SupressFinalize to 
                // take this object off the finalization queue 
                // and prevent finalization code for this object 
                // from executing a second time.
                GC.SuppressFinalize(this);
            }
    
            // Dispose(bool disposing) executes in two distinct scenarios. 
            // If disposing equals true, the method has been called directly 
            // or indirectly by a user's code. Managed and unmanaged resources 
            // can be disposed. 
            // If disposing equals false, the method has been called by the 
            // runtime from inside the finalizer and you should not reference 
            // other objects. Only unmanaged resources can be disposed. 
            protected virtual void Dispose(bool disposing)
            {
                // Check to see if Dispose has already been called. 
                if(!this.disposed)
                {
                    // If disposing equals true, dispose all managed 
                    // and unmanaged resources. 
                    if(disposing)
                    {
                        // Dispose managed resources.
                        component.Dispose();
                    }
    
                    // Call the appropriate methods to clean up 
                    // unmanaged resources here. 
                    // If disposing is false, 
                    // only the following code is executed.
                    CloseHandle(handle);
                    handle = IntPtr.Zero;
    
                    // Note disposing has been done.
                    disposed = true;
    
                }
            }
    
            // Use interop to call the method necessary 
            // to clean up the unmanaged resource.
            [System.Runtime.InteropServices.DllImport("Kernel32")]
            private extern static Boolean CloseHandle(IntPtr handle);
    
            // Use C# destructor syntax for finalization code. 
            // This destructor will run only if the Dispose method 
            // does not get called. 
            // It gives your base class the opportunity to finalize. 
            // Do not provide destructors in types derived from this class.
            ~MyResource()
            {
                // Do not re-create Dispose clean-up code here. 
                // Calling Dispose(false) is optimal in terms of 
                // readability and maintainability.
                Dispose(false);
            }
        }
        public static void Main()
        {
            // Insert code here to create 
            // and use the MyResource object.
        }
    }
    
    0 讨论(0)
  • 2020-11-21 04:53

    There should be no further calls to an object's methods after Dispose has been called on it (although an object should tolerate further calls to Dispose). Therefore the example in the question is silly. If Dispose is called, then the object itself can be discarded. So the user should just discard all references to that whole object (set them to null) and all the related objects internal to it will automatically get cleaned up.

    As for the general question about managed/unmanaged and the discussion in other answers, I think any answer to this question has to start with a definition of an unmanaged resource.

    What it boils down to is that there is a function you can call to put the system into a state, and there's another function you can call to bring it back out of that state. Now, in the typical example, the first one might be a function that returns a file handle, and the second one might be a call to CloseHandle.

    But - and this is the key - they could be any matching pair of functions. One builds up a state, the other tears it down. If the state has been built but not torn down yet, then an instance of the resource exists. You have to arrange for the teardown to happen at the right time - the resource is not managed by the CLR. The only automatically managed resource type is memory. There are two kinds: the GC, and the stack. Value types are managed by the stack (or by hitching a ride inside reference types), and reference types are managed by the GC.

    These functions may cause state changes that can be freely interleaved, or may need to be perfectly nested. The state changes may be threadsafe, or they might not.

    Look at the example in Justice's question. Changes to the Log file's indentation must be perfectly nested, or it all goes wrong. Also they are unlikely to be threadsafe.

    It is possible to hitch a ride with the garbage collector to get your unmanaged resources cleaned up. But only if the state change functions are threadsafe and two states can have lifetimes that overlap in any way. So Justice's example of a resource must NOT have a finalizer! It just wouldn't help anyone.

    For those kinds of resources, you can just implement IDisposable, without a finalizer. The finalizer is absolutely optional - it has to be. This is glossed over or not even mentioned in many books.

    You then have to use the using statement to have any chance of ensuring that Dispose is called. This is essentially like hitching a ride with the stack (so as finalizer is to the GC, using is to the stack).

    The missing part is that you have to manually write Dispose and make it call onto your fields and your base class. C++/CLI programmers don't have to do that. The compiler writes it for them in most cases.

    There is an alternative, which I prefer for states that nest perfectly and are not threadsafe (apart from anything else, avoiding IDisposable spares you the problem of having an argument with someone who can't resist adding a finalizer to every class that implements IDisposable).

    Instead of writing a class, you write a function. The function accepts a delegate to call back to:

    public static void Indented(this Log log, Action action)
    {
        log.Indent();
        try
        {
            action();
        }
        finally
        {
            log.Outdent();
        }
    }
    

    And then a simple example would be:

    Log.Write("Message at the top");
    Log.Indented(() =>
    {
        Log.Write("And this is indented");
    
        Log.Indented(() =>
        {
            Log.Write("This is even more indented");
        });
    });
    Log.Write("Back at the outermost level again");
    

    The lambda being passed in serves as a code block, so it's like you make your own control structure to serve the same purpose as using, except that you no longer have any danger of the caller abusing it. There's no way they can fail to clean up the resource.

    This technique is less useful if the resource is the kind that may have overlapping lifetimes, because then you want to be able to build resource A, then resource B, then kill resource A and then later kill resource B. You can't do that if you've forced the user to perfectly nest like this. But then you need to use IDisposable (but still without a finalizer, unless you have implemented threadsafety, which isn't free).

    0 讨论(0)
  • 2020-11-21 04:53

    Scenarios I make use of IDisposable: clean up unmanaged resources, unsubscribe for events, close connections

    The idiom I use for implementing IDisposable (not threadsafe):

    class MyClass : IDisposable {
        // ...
    
        #region IDisposable Members and Helpers
        private bool disposed = false;
    
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        private void Dispose(bool disposing) {
            if (!this.disposed) {
                if (disposing) {
                    // cleanup code goes here
                }
                disposed = true;
            }
        }
    
        ~MyClass() {
            Dispose(false);
        }
        #endregion
    }
    
    0 讨论(0)
  • 2020-11-21 04:53

    One problem with most discussions of "unmanaged resources" is that they don't really define the term, but seem to imply that it has something to do with unmanaged code. While it is true that many types of unmanaged resources do interface with unmanaged code, thinking of unmanaged resources in such terms isn't helpful.

    Instead, one should recognize what all managed resources have in common: they all entail an object asking some outside 'thing' to do something on its behalf, to the detriment of some other 'things', and the other entity agreeing to do so until further notice. If the object were to be abandoned and vanish without a trace, nothing would ever tell that outside 'thing' that it no longer needed to alter its behavior on behalf of the object that no longer existed; consequently, the 'thing's usefulness would be permanently diminished.

    An unmanaged resource, then, represents an agreement by some outside 'thing' to alter its behavior on behalf of an object, which would useless impair the usefulness of that outside 'thing' if the object were abandoned and ceased to exist. A managed resource is an object which is the beneficiary of such an agreement, but which has signed up to receive notification if it is abandoned, and which will use such notification to put its affairs in order before it is destroyed.

    0 讨论(0)
  • 2020-11-21 04:55

    Yep, that code is completely redundant and unnecessary and it doesn't make the garbage collector do anything it wouldn't otherwise do (once an instance of MyCollection goes out of scope, that is.) Especially the .Clear() calls.

    Answer to your edit: Sort of. If I do this:

    public void WasteMemory()
    {
        var instance = new MyCollection(); // this one has no Dispose() method
        instance.FillItWithAMillionStrings();
    }
    
    // 1 million strings are in memory, but marked for reclamation by the GC
    

    It's functionally identical to this for purposes of memory management:

    public void WasteMemory()
    {
        var instance = new MyCollection(); // this one has your Dispose()
        instance.FillItWithAMillionStrings();
        instance.Dispose();
    }
    
    // 1 million strings are in memory, but marked for reclamation by the GC
    

    If you really really really need to free the memory this very instant, call GC.Collect(). There's no reason to do this here, though. The memory will be freed when it's needed.

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