Imagine a:
public class Global : IDisposable
{
private static readonly List<IDisposable> Disposables = new List<IDisposable>();
public void ApplicationStart()
{
var heavyLifter = new HeavyLifter();
Disposables.Add(heavyLifter);
// register a few more
}
public void Dispose()
{
Disposables.ForEach(d => d.Dispose());
}
}
I am somewhat inexperienced with IDisposable. Is this a viable pa开发者_开发知识库ttern?
From what I understand, you're creating a component that will use (I assume) multiple resources that implement IDisposable
, and you're simply looking for a way to maintain a list of IDisposable
objects within your component and just iterate over the list rather than calling Dispose
on each item by name, is that correct?
If so, there's nothing wrong with that. However, your list should not be static
, it should be per-instance.
Well on one level IOC containers such as Unity can offer exactly this sort of functionality:
http://msdn.microsoft.com/en-us/library/ff663144.aspx
You delegate responsibility for creating objects to the IOC container, and can specify a LifetimeMangement option. This can include registering with the container for later disposal e.g. calling disposal on the container when an application shuts down, or when a form is closed.
For short lived objects you probably want to manage disposal yourself, but for longer lived objects an IOC type repository pattern with object disposal can work very well. Works well for me. :)
A good understanding of Disposal is always a good thing though.
You are working from the assumption that an outer class would have special knowledge of the lifetime of an object so that it call Dispose() at the right time. That rarely works, only the client code knows when it is done with an object.
With your class as written, you achieve the exact opposite goal, you'll keep the objects alive much longer than necessary. Not even the garbage collector could run the finalizer because you keep holding on to the object until some magic moment in time where all objects are ready to be disposed. The usual term for this is "memory leak". Don't do this.
Ostensibly, this approach makes sense when you cannot make the disposal thread-safe (for example when talking to COM objects, which MUST be released on the same thread). However, in practice, you would find that this approach leads to objects living longer than they should.
I would strive to make the disposal thread safe, that way you can call Dispose
from the finalizer and achieve truly automatic lifetime management. You have to be careful as might not be appropriate for some types of resources - like file or network handles, which might need tighter control. Otherwise, this is the best solution to this problem.
If the disposal HAS to be on the same thread then you are in a bit of a pickle. If the object needing disposing is in the model layer (as in business rules object) - it's quite likely it has layers of UI on top of, needing complex logic to dispose it (like events after a window is closed). It's a lose/lose situation and a choice between objects living forever (like your original solution) and complex disposal logic (which turns ugly pretty quickly).
Perhaps something to experiment with. You could separate the disposable resource into its own class and have the factory maintain a strong reference to it. The resource object maintains a weak reference back to business object. When the business object is finalized, WeakReference
will return null
, thus enabling you to make pass over the disposables and dump the ones that are no longer needed.
public class Global {
private static readonly List<Resource> Disposables = new List<Resource>();
public HeavyLifter GetHeavyLifter()
{
var resource = new HeavyLifterResource();
var heavyLifter = new HeavyLifter(resource);
resource.BusinessObject = heavyLifter;
Disposables.Add(resource);
}
public void DisposeAll()
{
Disposables.ForEach(d => d.CleanUp());
}
}
public abstract class Resource : IDisposable {
WeakReference<object> m_BusinessObject;
public WeakReference<object> BusinessObject {get;set;}
public CleanUp() {
if (!m_BusinessObject.IsAlive)
Dispose();
}
}
public HeavyLifter {
public HeavyLifter (Disposable d) {
m_resourceObj = d;
}
HeavyLifterResource m_resourceObj;
}
public class HeavyLifterResource :Resource {
public void Dispose() {
//disposal
}
}
Let me again reiterate that the above approach is only appropriate for a certain class of objects. You wouldn't want to handle your network connections in such fuzzy way, but you can, for example, do this for business objects that need a network connection to dispose themselves. Even then, this is only appropriate when such connection is persistent through the lifetime of the application.
Your code implies that HeavyLifter implements IDisposable. So all you need to do is call its Dispose method in the Application_End eventhandler.
A disposal registry may sometimes be useful as a concrete type, in cases where one knows that an object one is creating will have a lifetime that's tied to another object. If a class is designed so that its creation can only take place wrapped within a particular factory method, and if one doesn't mind using ThreadStatic
variables, one may even be able to replace:
DisposableThing Foo; // At one point in the class
...
Foo = new DisposableThing(); // In the constructor
...
DisposableThing.Dispose; // In `Dispose(bool)`
with
DisposableThing = DisposeProtector.Register(new DisposableThing());
Taking care of declaration, initialization, and cleanup, all in the same line.
A very nice clean pattern, and one which even better in vb.net (no ThreadStatic variables required, and the disposal-registration method can be a member of the base type). Probably a little too icky to be worthwhile in C#, but in vb.net it could be a nice pattern. Note that if the constructor is properly wrapped, Dispose
will get called on everything that was registered in its scope [but not a nested scope] when either Dispose
is called on the class, or the constructor throws, the latter being a situation where it is otherwise difficult to avoid leaks.
精彩评论