Are IDisposable objects meant to be created and disposed just once?
I'm using a huge binary tree like structure whose nodes may or may not make use of unmanaged resources. Some of these resources may take up a lot of memory, but only a few of them will be in use at a time. The initial state of the tree can be seen as 'dormant开发者_如何学Go'.
Whenever a node is accessed, that specific node and its children will 'wake up' and lazily acquire their assigned resources. Likewise, accesing a different branch in the tree will put the currently active branch to sleep causing its resources to be released. This means that any given node can be woken up and put to sleep once and again at any given time.
I'm currently taking advantage of the IDisposable interface to achieve this. It's being quite useful because there are a lot of cases where I need to create small branches that will be used locally, and the 'using' keyword comes really handy ensuring that no resource will be left open accidentally.
Am I ok implementing IDisposable on objects that don't really get disposed but sort of put to sleep?
Thanks in advance.
Edit: Thanks all for all the clever answers. I liked the idea of disposing of a resource's access instead of the resource itself. Now I'm in search of a better name for the function responsible for the clean up. (Any ideas other than Release() or Sleep()? Thanks again.
It's not entirely clear from the IDisposable.Dispose documentation, which includes this (emphasis mine):
Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
but also this:
If an object's Dispose method is called more than once, the object must ignore all calls after the first one. The object must not throw an exception if its Dispose method is called multiple times. Instance methods other than Dispose can throw an ObjectDisposedException when resources are already disposed.
The latter suggests that it really shouldn't be used for a "reset" sort of operation, which is what I think you're after. (I'm not sure that your "put to sleep" terminology really helps here; am I right in saying you're really disposing of all the actively-acquired resources in all subnodes?)
Of course, this is just by convention - you can make your code do whatever you like. However, I think most developers would find it somewhat strange.
I can see what you're trying to do, but I'm not sure the best way of doing it...
@Jon Skeet has answered the question really well, but let me chip in with a comment that I feel should be an answer by its own.
It is quite common to use the using
code block to temporarily acquire some resource, or enter some form of scoped code you want a clean exit from. I do that all the time, in particular in my business logic controllers, where I have a system that postpones change-events until a block of code has executed, to avoid side-effects multiple times, or before I'm ready for them, etc.
In order to make the code look more obvious to the programmer that uses it, you should look at using a temporary value that you use
instead of the object that has the resource, and returning it from a method name that tells the programmer what it is doing, ie. acquiring some resources temporarily.
Let me show an example.
Instead of this:
using (node) { ... }
you do this:
using (node.ResourceScope()) { ... }
Thus, you're not actually disposing of anything more than once, since ResourceScope
will return a new value you dispose of, and the underlying node will be left as is.
Here's an example implementation (unverified, typing from memory):
public class Node
{
private Resource _Resource;
public void AcquireResource()
{
if (_Resource == null)
_Resource = InternalAcquireResource();
}
public void ReleaseResource()
{
if (_Resource != null)
{
InternalReleaseResource();
_Resource = null;
}
}
public ResourceScopeValue ResourceScope()
{
if (_Resource == null)
return new ResourceScopeValue(this);
else
return new ResourceScopeValue(null);
}
public struct ResourceScopeValue : IDisposable
{
private Node _Node;
internal ResourceScopeValue(Node node)
{
_Node = node;
if (node != null)
node.AcquireResource();
}
public void Dispose()
{
Node node = _Node;
_Node = null;
if (node != null)
node.ReleaseResource();
}
}
}
This allows you to do this:
Node node = ...
using (node.ResourceScope()) // first call, acquire resource
{
CallSomeMethod(node);
} // and release it here
...
private void CallSomeMethod(Node node)
{
using (node.ResourceScope()) // due to the code, resources will not be 2x acquired
{
} // nor released here
}
The fact that I return a struct, and not IDisposable
means that you won't get boxing overhead, instead the public .Dispose method will just be called on exit from the using
-block.
I would say, "no". IDisposable
has a particular purpose, and "sleeping" isn't it.
Sure, you should not release resources if you didn't got them, so in this case your Dispose method will do nothing.
Maybe, you should use a composite object with IDisposable inside and allocate/dispose resources in this Property/Field. So you will woke (allocate new object with resources) and put to sleep (dispose resources) while having your node alive.
In this case you steel need to derive your node from IDisposable because when you have property/field with IDisposable, the container should implement IDisposable too.
No, this doesn't seem like an appropriate use of IDisposable.
A quick thought on what you could do; Implement another object that is IDisposable that can contain the data that is loaded, and return that data from a method call on your object; ex:
using(var wokenUpData = dataContainer.WakeUp())
{
// access the data using wokenUpData
...
}
Sounds like you just need to add an extra level of indirection.
What is being confused here are object lifetimes. On the one hand you have a long lived object (node
), which is not always using other resources. On the other hand you have those other resouces that a node may use while "awake", and will relinquish (if I got you right) when going back to sleep (by virtue of another node being selected).
So it sounds like you have two lifetime concepts, and these could be modelled by introducing another object to more directly manage the resources.
So, put your "lazily aquired resources" into the new resource management object, which itself eagerly aquires the resources - and disposes of them in dispose()
. Then your node can just create the resource management objects as needed (when waking), and dispose of them when finished (going back to sleep)- and the lifetimes are not confused.
精彩评论