Вопрос

Consider we are using some unmanaged resources.

The most common approach is:

//With IDisposable
using (MemoryStream memoryStream = new MemoryStream())
{
   //Operate with memory stream
}

But cannot we write the following?

//With destructor called at the end of a block
{
    MemoryStream memoryStream = new MemoryStream();
    //Operate with memory stream
}
  • What is the main goal of IDisposable?

  • Is it only a design pattern for finalization logic separation?

  • And does, for example, Java, provide something similar?

Это было полезно?

Решение

  • What is the main goal of IDisposable?

  • Is it only a design pattern for finalization logic separation?

Its goal is simply to be a standard interface to call that logic. The recommended implementation separates manual disposing (Dispose(true)) from Dispose called from the finalizer (Dispose(false)) simply for efficiency -- it's redundant to dispose internal fields from within the finalizer because, since finalizers can't be called manually, we know at that point they must be already being collected.

Manual disposal is needed because garbage collection is not immediate (and you can't force collection of an individual object alone). This is more than just an efficiency concern, because it's possible that the unmanaged resource doesn't support multiple access, so another object accessing it later will fail if the first was not yet collected. Collection isn't even guaranteed, because as Ricibob showed, putting the code inside an explicit scope doesn't stop an external object from getting its own reference.

  • And does, for example, Java, provide something similar?

using is simply an auto-implemented "try ... finally" block that calls a pre-defined method.

Java includes this feature as an extension of the try statement itself. This works the same as using, except that it also allows you to add your own catch and finally blocks without needing to wrap it in an additional try block.

Python has context managers which are a more flexible version of this. A context manager can define specific exception handling as well as the finally, and may return a different object than what was passed in -- that is, this would be possible:

with CustomDisposer(MemoryStream()) as memoryStream:

where the CustomDisposer object is responsible for the dispose implementation, yet it returns the MemoryStream as the resource to be assigned to the memoryStream variable.

Ruby has a yield statement that allows a function to wrap a code block and optionally give a parameter to the block, so you can implement this by passing the given object to the block and then calling dispose in ensure (finally equivalent):

def using(o)
    yield o
ensure
    o.dispose
end

using MemoryStream.new do |memoryStream|
    #Operate with memory stream
end

Of course, since this is part of a function definition, there is no need for a dedicated using function -- it could be directly implemented in the MemoryStream.Open method for example.

Другие советы

The destructor does not get called at the end of the block, but whenever the GC destroys the object. You do not have control over when this happens. Sometimes you need that kind of control. That's when IDisposable comes into play.

Consider:

var sc = new StreamConsumer();
{
    var memoryStream = new MemoryStream();
    //Operate with memory stream
    sc.Stream = memoryStream;
}
sc.DoStuffWithStream();

Here it the GC job to determine when we're actually done with memoryStream and it can be destroyed. That is not always staight forward (as C++ developers well know...) We don't know when the GC will do this - it is not deterministic in time.

Using/IDisposable is not about object destruction but about controlling objects access to resources in a timely deterministic way. When we apply a using were saying that outside this statement I know the resource (file, memory mapped file, socket etc) is free and useable by other objects. If I try to access a disposed object (out side its using scope) because there are other objects that hold refs to it - then I'll get an exception.

It's also important to note that the finalizer (descructor) is actually not guaranteed to run at all. Usually when a finalizer is implemented, all it does is call Dispose accounting for the off-chance that the consumer forgot to put the object in a using block. This pattern can be implemented if you suspect a memory leak on an object if it is not disposed.

Microsoft even gave this practice a name: http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx

The gist is: In C# you should almost always use IDisposable.

The fundamental purpose of the GC is to ensure that no reference to any object will ever turn into anything other than reference to that object. Thus, it must keep objects around if it can see any possible means via which references to them might be accessed. Because objects to which strong rooted references exist are generally "useful", and objects to which no strong rooted references exist are generally not "useful", the garbage collector can also to some extent identify objects which are useless and should have all references to them eliminated.

Unfortunately, even if the GC were perfectly responsive and could instantly identify when the last strong reference to any given object disappeared, that wouldn't mean it could reliably identify useless objects. If X holds a strong reference to Y, that usually means that if something is interested in X, something will be interested in Y. In some cases, however, it will instead mean that Y is interested in receiving some sort of notifications from X. Suppose X is signed up receive timer-tick events from a static timer-tick handler, and Y increments an Int64 counter each time an event is received from X and some condition is true. Other objects with a reference to that counter can check the state of the counter. As long as some reference to Y exists other than the one held by X, the timer event should continue to fire and X should continue to update Y's counter. Once all other references to Y cease to exist, Y's counter will be useless, as will Y's efforts to update it. This will in turn make X's notifications useless, which will make the static timer events received by X useless. The timer resources used by should be released, and X detached from the static handler; X and Y should then cease to exist. All very nice and orderly except for one problem: even though the usefulness of X depends upon the usefulness of Y, and the usefulness of the timer depends upon the usefulness of Y, the object references point the other way. While it's possible to arrange things so that the timer will get released if Y is abandoned, doing so adds significant overhead to the code which needs to run when the timer is in use.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top