IDisposable implementation - What should go in 

2019-04-04 04:07发布

I have been fixing some memory leak issues in a winforms application and noticed some disposable objects that are not Disposed explicitly (developer hasn't called Dispose method). Implementation of Finalize method also doesn't help because it doesn't go in if (disposing) clause. All the static event unregistering and collection clearing have been put in if (disposing) clause. The best practice is calling the Dispose if the object is disposable, but unfortunately this happens sometimes

If there are unmanaged objects, static event handlers and some managed collections that needs to clear when disposing. What's the way to decide what should go in and what should go out of if (disposing) clause.

Dispose method.

// 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)
{
    if (!disposed)
    {
        if (disposing)
        {
            // Free other state (managed objects).
        }

         // Free your own state (unmanaged objects).
         // Set large fields to null.
         disposed = true;
     }
 }

It says managed objects should in if (disposing) which executes normally only when explicitly call Dispose method by the developer. If the Finalize method has been implemented and developer forgets to call the Dispose method the execution that comes here through the Finalizer does not go in if (disposing) section.

Below are my questions.

  1. If I have static event handlers that causes memory leaks where should I un-register them? In or out of if (disposing) clause?

  2. If I have some collections that causes memory leaks where should I clear them? In or out of if (disposing) clause?

  3. If I am using third party disposable objects (eg: devExpress winform controls) that I am not sure whether they are managed or unmanaged objects. Let's say I want to dispose them when disposing a form. How can I know what are managed and what are non-managed objects? Being disposable doesn't say that? In such cases how to decide what should go in and what should go out of if (disposing) clause?

  4. If I am not sure something managed or unmanaged what can be the bad consequences of disposing/clearing/unregistering-events out of the if (disposing) clause? Let's say it checks for null before disposing?

Edit

What I mean as event un-registering is something like below. Publisher is a long lived instance and below line is in the subscriber's constructor. In this case subscriber need to unregister the event and dispose before the publisher.

publisher.DoSomeEvent += subscriber.DoSomething;

6条回答
Melony?
2楼-- · 2019-04-04 04:16

Unless the sole purpose of a class is to encapsulate some resource(*) which needs to be cleaned up if abandoned, it shouldn't have a finalizer, and Dispose(bool) should never be called with a value of False, but calling Dispose(False) should have no effect. If an inherited class would need to hold a resource requiring cleanup if abandoned, it should encapsulate that resource into an object devoted solely to that purpose. That way, if the main object gets abandoned and nobody else holds any reference to the object encapsulating the resource, that object can perform its cleanup without having to keep the main object alive for an extra GC cycle.

Incidentally, I dislike Microsoft's handling of the Disposed flag. I would suggest that the non-virtual Dispose method should use an integer flag with Interlocked.Exchange, to ensure that calling Dispose from multiple threads will only result in the dispose logic being performed once. The flag itself may be private, but there should be a protected and/or public Disposed property to avoid requiring every derived class to implement its own disposal flag.

(*) A resource isn't some particular type of entity, but rather a loose term that encompasses anything a class may have asked some outside entity to do on its behalf, which that outside entity needs to be told to stop doing. Most typically, the outside entity will have granted the class exclusive use of something (be it an area of memory, a lock, a GDI handle, a file, a socket, a USB device, or whatever), but in some cases the outside entity may have been asked to affirmatively do something (e.g. run an event handler every time something happens) or hold something (e.g. a thread-static object reference). An "unmanaged" resource is one which will not be cleaned up if abandoned.

BTW, note that while Microsoft may have intended that objects which encapsulate unmanaged resources should clean them up if abandoned, there are some types of resources for which that really isn't practical. Consider an object that stores an object reference in a thread-static field, for example, and blanks out that object reference when it is Dispose'd (the disposal would, naturally, have to occur on the thread where the object was created). If the object gets abandoned but the thread still exists (e.g. in the threadpool), the target of the thread-static reference could easily be kept alive indefinitely. Even if there aren't any references to the abandoned object so its Finalize() method runs, it would be difficult for the abandoned object to locate and destroy the thread-static reference sitting in some thread.

查看更多
男人必须洒脱
3楼-- · 2019-04-04 04:25

If I have static event handlers that causes memory leaks where should I un-register them? In or out of if (disposing) clause?

Dispose method is called on instances where as static event handler are used at class level. So you should not un-register them at all in dispose. Usually static event handler should be un-register when class is unloading or at some point during the execution of application you derive that this event handler is no more required.

For all manages and un-managed resources better implement IDisposable pattern. See here http://msdn.microsoft.com/en-us/library/fs2xkftw%28VS.80%29.aspx and Finalize/Dispose pattern in C#

查看更多
叛逆
4楼-- · 2019-04-04 04:29

It sounds like you mainly have managed objects, i.e. objects that implement IDisposable. Unmanaged code would be things like IntPtr's or handles, which would normally mean calling unmanaged code or P/Invoke to get to them.

  1. As Maheep pointed out, Dispose is not meant for this. When an object is done receiving events it should unregister itself. If that's not possible, consider using WeakReferences instead.

  2. This probably shouldn't go in dispose unless these collections contain objects that need to be disposed. If they are disposable objects then it should go in the "if disposing" block and you should call dispose on each item in the collection.

  3. If it implements IDisposable it's managed

  4. You shouldn't access other managed code objects when called by the finalizer which is what being outside the "if (disposing)" block means.

查看更多
放荡不羁爱自由
5楼-- · 2019-04-04 04:33

The key to remember here is the purpose of IDisposable. It's job is to help you deterministically release resources that your code is holding, before the object is garbage collected. This is actually why the C# language team chose the keyword using, as the brackets determine the scope that the object and it's resources are required for by the application.

For instance, if you open a connection to a database, you want to release that connection and close it ASAP after you have finished with it, rather than waiting for the next garbage collection. This is where and why you implement a Disposer.

The second scenario is to assist with unmanaged code. Effectively this is anything to do with C++/C API calls to the operating system, in which case you are responsible for ensuring that the code isn't leaked. As much of .Net is written to simply P/Invoke down to the existing Win32 API this scenario is quite common. Any object which encapsulates a resource from the operating system (e.g. a Mutex) will implement a Disposer to allow you to safely and deterministically release it's resources.

However these APIs will also implement a destructor, to guarantee that if you don't use the resource correctly that it will not be leaked by the operating system. When your finalizer is called, you do not know whether or not other objects you were referencing have already been garbage collected, which is why it is not safe to make function calls upon them (as they could throw NullReferenceException), only the unmanaged references (which by definition cannot be garbage collected) will be available to the finalizer.

Hope that helps a bit.

查看更多
仙女界的扛把子
6楼-- · 2019-04-04 04:36

Broadly, managed resources are disposed inside if (disposing) and unmanaged resources outside of it. The dispose pattern works as such:

  1. if (disposed) {

    If this object is already disposed, don't dispose of it a second time.

  2. if (disposing) {

    If disposal was requested programatically (true), dispose of managed resources (IDisposable objects) owned by this object.

    If disposal was caused by the garbage collector (false), do not dispose of managed resources because the garbage collector may have already disposed of the owned managed resources, and will definitelty dispose of them before the application terminates.

  3. }

    Dispose of unmanaged resources and release all references to them. Step 1 ensures this only happens once.

  4. disposed = true

    Flag this object as disposed to prevent repeated disposal. Repeated disposal may cause a NullReferenceException at step 2 or 3.

Question 1
Don't dispose of them in the Dispose method at all. What would happen if you disposed of multiple instances of the class? You'd dispose the static members each time, despite them already being disposed. The solution I found was to handle the AppDomain.DomainUnloaded event and perform static disposal there.

Question 2
It all depends if the items of the collection are managed or unmanaged. It's probably worth creating managed wrappers that implement IDisposable for any unmanaged classes you are using, ensuring all objects are managed.

Question 3
IDisposable is a managed interface. If a class implements IDisposable, it's a managed class. Dispose of managed objects inside if (disposing). If it doesn't implement IDisposable, it is either managed and does not require disposing, or is unmanaged and should be disposed outside of if (disposing).

Question 4
If the application terminates unexpectedly, or doesn't use manual disposal, the garbage collector disposes of all objects in random order. The child object may be disposed before it's parent is disposed, causing the child to be disposed a second time by the parent. Most managed objects can safely be disposed multiple times, but only if they've been built correctly. You risk (though, unlikely) causing the gargabe collection to fail if an object is disposed multiple times.

查看更多
聊天终结者
7楼-- · 2019-04-04 04:37

What should go in 'if (disposing)'

All Managed objects should go inside if(disposing) clause. Managed objects should not go out side of it (which will be executed through the finalization).

The reason is that Garbage collectors finalization process can execute the Dispose(false) if that class has a Destructor. Normally there is a Destructor only if there are unmanaged resources.Garbage collector's finalization doesn't have a particular order to execute the Finalize method. So, other managed objects may not be in memory by the time finalization occurs.

查看更多
登录 后发表回答