by Zoran Horvat
The Dispose pattern is one of the few specific patterns introduced by the .NET platform. It is mandatory whenever an application operates on non-managed resources. Typical examples are operating system handles such as file handles, connections (network, database connections, etc.). More specific uses relate to holding operating system-managed image data or memory allocations and similar.
This article will not deal with implementing the Dispose pattern. Instead, we will try to shed some light on code which is consuming a disposable object, i.e. the code which is mandated to invoke its Dispose method. As you will witness, that will open some interesting questions, the most important in my opinion being: Why should a consumer care to know that the resource is disposable? It is tempting to dismiss the issue by deciding that the consumer should not know that the resource is disposable. But the problem won’t give way so easy.
Let’s start with a simple example: Reading a textual file. That will surely involve some disposable resources.
Below is the code segment, which you must have already seen (or coded yourself) so many times. It is opening a FileStream to a file on the disk, and then reading its content, one line at a time, using the TextReader. Both the FileStream and the TextReader object are disposable, and that is where our troubles are beginning.
public void PrintOut(FileInfo source)
{
using (FileStream input = source.OpenRead())
{
using (TextReader textInput = new StreamReader(input))
{
while (textInput.ReadLine() is string line)
{
Console.WriteLine(line);
}
}
}
We are first constructing the input object, which is disposable, and hence enclosed in the using block. Then, the input object is used to construct the textInput reader object, also disposable.
Once we are done processing data, we need to dispose objects in the reverse order of their construction. This is important, because the inner object, textInput, might internally hold onto structures of the outer object, input, and therefore the outer object shouldn’t be disposed before these structures are released.
One might ask: Why does this method receive the FileInfo in the first place – why not pass the TextReader right away? Here is what such a function would look like:
public void PrintOut(TextReader textInput)
{
while (textInput.ReadLine() is string line)
{
Console.WriteLine(line);
}
}
Wouldn’t this save us the trouble of completing the Dispose pattern in the consuming code?
Yes – obviously – but! there is a slight omission in that overly enthusiastic view. The problem is that prior complexity was the consequence, not the cause. The consequence of our primary concern when the Dispose pattern is involved, and that is to control the lifetime of the target object.
The second implementation above, the one receiving the TextReader object, is not controlling its lifetime. We will immediately expose what makes that a mistake, and that will finally bring us to full understanding of the Dispose pattern, as observed from the consumer’s point of view.
When we see a type implementing the IDisposable interface, i.e. implementing the Dispose pattern, that is because it is dealing with a scarce resource, the use of which must follow the two-phase protocol. In the first phase, the resource is allocated and handed over to the consuming code. In the second phase, the resource is deallocated, i.e. handed back to the environment (typically, the operating system).
Should the consumer forget or fail to complete the releasing phase, the amount of the scarce resource available to other consumers would be limited. We call that a resource leak. The imaginary bucket containing all the resources available looks to be leaking, since the total resource pool is gradually shrinking as time progresses.
But that is only one half of the problem. The other side of the same coin reveals that resources might deplete even if there is no leak. If legitimate consumers are holding on to resources for time intervals much longer than necessary, then other legitimate consumers might not get hold of the scarce resource when they need it.
This analysis finally brings us to the full definition of the Dispose pattern, as viewed from the consuming end. Any consumer of a disposable object must invoke its Dispose method when it’s done using the object. Consumer must restrict the lifetime of a disposable object to the shortest possible interval of time.
While the first requirement is self-evident (not calling the Dispose method means the resource leak), the second requirement might occasionally pass under the radar. It is equally important, nevertheless.
Typical methods to shorten the lifetime of a disposable object is to postpone its creation until all necessary information has been gathered. Only then, the object would be constructed, used with no delay, and disposed of immediately. That is what every consumer of every disposable type should always do.
If we return to suggested solution (passing the disposable object to a public method) we would immediately recognize that that could make lifetime of a disposable object indefinitely long. But we needed a definite, predictable, and short lifetime of any disposable object.
public void PrintOut(TextReader textInput)
{
...
}
Notice that the method above is declared as public, which means that it will be accessed from other types. By the principle of encapsulation, callers would not be able to tell what exactly the method is doing when called and, especially, how long that will take. Looking from the outside, there is no guarantee whatsoever that the lifetime of the disposable object will be minimized. And that makes this a bad design.
This conclusion is pushing us back to square one, to the solution with which we have started this analysis. But all is not lost, because in the meantime we have understood the problem better. It is not construction of a disposable object what we are interested to control, but only the time span during which the object exists.
When said that way, a different design comes to mind. Design based on the Factory pattern. Why not accept a factory that will create the disposable object? This is the idea which we will pursue next, as it will let us control the lifetime of the disposable object.
Here is the design of the public method which requests a factory of the disposable object.
public void PrintOut(Func<FileStream> sourceFactory)
{
using (FileStream input = sourceFactory())
{
using (TextReader textInput = new StreamReader(input))
{
while (textInput.ReadLine() is string line)
{
Console.WriteLine(line);
}
}
}
}
The key difference to previous design is that this time we are not interested to know how exactly the FileStream object comes to be. That is someone else’s problem, from this method’s point of view. In fact, this method is more general than the previous ones, since it can be called from any execution scenario which has access to a FileStream, not only from scenarios with files. Anyway, if file is what we are having, then this new method would be invoked as:
PrintOut(() => source.OpenRead());
We can even choose to expose the public method which accepts FileInfo as a convenience:
public void PrintOut(FileInfo source) =>
this.PrintOut(source.OpenRead);
public void PrintOut(Func<FileStream> sourceFactory)
{
...
}
Observe that both methods are now public. We were safe to expose them as public, because they can guarantee to control all the disposable objects that might appear in the process. The very signatures of these methods are promising to manage disposable objects correctly. (For the sake of completeness, let’s say that the method might as well forget to dispose an object – but then that would be a bug, not a design issue.)
This design brings us to the brink of an important discovery. By this point, we have provided a design which guarantees that exactly one party will both create and dispose the disposable object. That is the good design. And it opens the door for the next improvement: Why not let the factory somehow dispose its product after it’s used? This idea is surely worth investigating.
To understand the idea which we are pursuing, let’s refactor the implementation to make separate steps more obvious.
public void PrintOut(FileInfo source) =>
this.PrintOut(source.OpenRead);
public void PrintOut(Func<FileStream> sourceFactory)
{
using (FileStream input = sourceFactory())
{
PrintOut(input);
}
}
private void PrintOut(FileStream input) =>
PrintOut(() => new StreamReader(input));
public void PrintOut(Func<TextReader> readerFactory)
{
using (TextReader textInput = readerFactory())
{
PrintOut(textInput);
}
}
private void PrintOut(TextReader textInput)
{
while (textInput.ReadLine() is string line)
{
Console.WriteLine(line);
}
}
This time, we have five separate methods, each doing only one thing. Note that the two methods receiving disposable objects are private. They have to be, because they cannot guarantee that the object they use will indeed be disposed without delays. Therefore, these methods must be used under controlled conditions, and those conditions are established by public methods.
Public methods, on the other hand, are all either receiving non-disposable objects (such as the FileInfo object) or factories of disposable objects. The latter are declaring the intention to use the disposable object and then dispose of it in the shortest possible time, which is all in the spirit of the Dispose pattern.
When designed this way, it becomes obvious that the using code pattern in methods receiving a factory should become mandatory. Look at the PrintOut(Func<FileStream>) and PrintOut(Func<TextReader>) methods – they both have the same structure. Construct the disposable object, use it in the operation which depends on it, and then dispose of it right away (by leaving the using block).
The using block also ensures that the Dispose method will be invoked even when an exception is thrown. In fact, the using block is unrolled into a try-finally statement, so that the Dispose method is always invoked from the finally block.
This is what the using block really looks like before code which consumes the FileInfo object is compiled.
FileStream input = fileInfo.OpenRead();
try
{
// do the work
}
finally
{
if (input != null)
input.Dispose();
}
Now that we have the full understanding of how the objects produced by factories are disposed, we are ready to see that something is missing in methods receiving factories. Take a look at one of them again, and you will see what I mean:
public void PrintOut(Func<FileStream> sourceFactory)
{
using (FileStream input = sourceFactory())
{
PrintOut(input);
}
}
Through all the explanations given so far, I have insisted that this method is guaranteeing that the disposable object will be disposed. It guarantees that by taking the responsibility of the disposable object’s lifetime. However, that guarantee is only implicit. There is nothing to warn us if we simply forgot to dispose the object:
public void PrintOut(Func<FileStream> sourceFactory) =>
PrintOut(sourceFactory());
This implementation will compile just fine. And it will be incorrect.
This reveals that the factory function, although a step in the correct direction by itself, is not strong enough for the purpose. We need an object which is even more limiting than the factory function – an object which will do everything the factory function is doing (i.e. create the disposable object), but also dispose its result after use. Only then will this PrintOut method truly guarantee that the Dispose pattern will be applied in its entirety.
Here is the signature and implementation of the desired method:
public void PrintOut(Disposable<FileStream> source) =>
source.Use(input => this.PrintOut(input));
The using block now becomes implicit. This new type, Disposable<T> is intended to wrap the factory of a disposable object and the disposal of its result into one atomic operation. There will be no bugs in implementation and no misunderstandings regarding the use of any disposable type.
By this point we have designed the API, the interface of a method which guarantees that the Dispose pattern is implemented. That was the most difficult step – designing the interface of a system we want to have implemented. With that done, truly implementing the system with said constraints is more than easy.
With no further ado, here is the complete implementation of the Disposable<T> class, and its associated helper static Disposable class which serves as a convenient factory.
public class Disposable<T>
where T : IDisposable
{
private Func<T> Factory { get; }
internal Disposable(Func<T> factory)
{
this.Factory = factory;
}
public void Use(Action<T> action)
{
using (T target = this.Factory())
{
action(target);
}
}
public TResult Use<TResult>(Func<T, TResult> map)
{
using (T target = this.Factory())
{
return map(target);
}
}
}
public static class Disposable
{
public static Disposable<T> Of<T>(Func<T> factory)
where T : IDisposable
=> new Disposable<T>(factory);
}
The Disposable<T> class exposes two variants of the Use method – one receiving an action, and another receiving a Func delegate. Both variants guarantee that the target object will be constructed, passed to the delegate, and then disposed.
The non-generic Disposable class is only offering a convenient static factory function which elevates a common factory function to a disposable wrapper.
Now that we have the disposable wrapper available, we can try to use it to print content of a file to the console. Below is the entire code which performs that task.
public void PrintOut(Disposable<FileStream> inputFactory) =>
inputFactory.Use(this.PrintOut);
private void PrintOut(FileStream input) =>
this.PrintOut(Disposable.Of<TextReader>(() => new StreamReader(input)));
public void PrintOut(Disposable<TextReader> readerFactory) =>
readerFactory.Use(this.PrintOut);
private void PrintOut(TextReader textInput)
{
while (textInput.ReadLine() is string line)
{
Console.WriteLine(line);
}
}
This code segment is operating on two disposable objects, one of type derived from Stream, and another derived from StreamReader. Both objects will live for as long as it takes to effectively print out the file content to the console, and then they will be disposed with no unnecessary delay.
If you wanted to call these functions, you would have to provide a concrete Disposable object:
FileInfo source = new FileInfo(@"C:\Temp\temp.txt");
this.Read(Disposable.Of(source.OpenRead));
On the other hand, the entire code segment above is not disposing any objects. It is merely declaring the intention to use disposable objects, and that is where its responsibilities end. We could say that Disposable<T> type is allowing a declarative implementation of the Dispose pattern – just declare the method which receives a Disposable<T>, and the entire pattern will be implemented on your behalf.
When using a disposable object to load the data, we have one additional concern to take care of. Namely, the disposable object will be used to return data, and then it will be disposed before those data are handed over to the ultimate caller.
This usage fails in one corner case, and that is when the data produced by the disposable object are not immediately materialized. Take the IEnumerable<T> interface and LINQ as an example. You may feed the data from a disposable object into a lazy-evaluated IEnumerable<T> stream, hoping to materialize the stream’s content on demand. The problem is that by the time the consumer decides to effectively access the stream’s content, the source of that content will already be disposed.
A typical example when this happens is loading the data from the database and then executing the lazy-evaluated stream the data access object returns. Since the data access object is disposable (as it must be utilizing a disposable database connection), by the time the application decides to effectively pull the data, the database connection will already be closed.
To cut the long story short, below is the proper way to consume a disposable data source. The principle is very simple, and it boils down to materializing all the resulting data before disposing the source object.
public List<string> Read(Disposable<FileStream> inputFactory) =>
inputFactory.Use(this.Read);
private List<string> Read(FileStream input) =>
this.Read(Disposable.Of<TextReader>(() => new StreamReader(input)));
public List<string> Read(Disposable<TextReader> readerFactory) =>
readerFactory.Use(this.Read);
private List<string> Read(TextReader textInput)
{
List<string> data = new List<string>();
while (textInput.ReadLine() is string line)
{
data.Add(line);
}
return data;
}
Once again, we are facing the code which only declares its intention to consume objects that implement the Dispose pattern. To effectively use this code, we would have to produce a Disposable object:
FileInfo source = new FileInfo(@"C:\Temp\temp.txt");
int totalLength =
Read(Disposable.Of(source.OpenRead))
.Sum(line => line.Length);
This article has demonstrated how easy it can be to introduce concepts in programming when they are well defined and isolated from surrounding concepts. It only takes an effort to define the concept properly, especially its boundaries, and the corresponding programming type will emerge almost on its own.
That brings up the question of programming types, then. The goal of the design method demonstrated in this article is to get hold of a type which clearly specifies the concept through its bare existence. There is nothing special the consumer of this type should do to ensure that the critical concept is implemented – the concept will be implemented at run time if there is an object of the type which wraps that concept.
This wonderful idea brings us back to the roots of object-oriented programming, and its fundamental principle: If you have an object, then everything is alright.
Having well-separated and well-defined types in an object-oriented system makes it possible for us to start coding declaratively. The code given above demonstrates that the client-side disposable protocol can be implemented entirely declaratively.
The method which by all means must implement the client’s part of the Dispose pattern will simply declare that intent through its signature. Programming doesn’t come much easier than that, you will agree.
If you wish to learn more, please watch my latest video courses
In this course, you will learn the basic principles of object-oriented programming, and then learn how to apply those principles to construct an operational and correct code using the C# programming language and .NET.
As the course progresses, you will learn such programming concepts as objects, method resolution, polymorphism, object composition, class inheritance, object substitution, etc., but also the basic principles of object-oriented design and even project management, such as abstraction, dependency injection, open-closed principle, tell don't ask principle, the principles of agile software development and many more.
More...
In this course, you will learn how design patterns can be applied to make code better: flexible, short, readable.
You will learn how to decide when and which pattern to apply by formally analyzing the need to flex around specific axis.
More...
This course begins with examination of a realistic application, which is poorly factored and doesn't incorporate design patterns. It is nearly impossible to maintain and develop this application further, due to its poor structure and design.
As demonstration after demonstration will unfold, we will refactor this entire application, fitting many design patterns into place almost without effort. By the end of the course, you will know how code refactoring and design patterns can operate together, and help each other create great design.
More...
In four and a half hours of this course, you will learn how to control design of classes, design of complex algorithms, and how to recognize and implement data structures.
After completing this course, you will know how to develop a large and complex domain model, which you will be able to maintain and extend further. And, not to forget, the model you develop in this way will be correct and free of bugs.
More...
Zoran Horvat is the Principal Consultant at Coding Helmet, speaker and author of 100+ articles, and independent trainer on .NET technology stack. He can often be found speaking at conferences and user groups, promoting object-oriented and functional development style and clean coding practices and techniques that improve longevity of complex business applications.