Last time then, we were talking about
garbage collection, and in that I was using a finalizer purely so we could output a message to
see when garbage collection was happening - which is something that's quite interesting, but not
what you do in normal programming. So this week, I thought we'd look at the situations where
we use finalize and we'll actually discover that in real life, it's pretty rare that you
use it. Finalize is very closely connected with another much more commonly used method
called Dispose, and so we'll look at how the two of those interact. So what I've got
here in this program is a class called 'PureManagedClass' and that just has a member of
this type 'StreamWriter' for writing text out to a file. And we can see there we've got a method
called 'StartWriting'. So if we want to use it, that is what initializes it. And as you can
imagine, one of the things you've got to do with StreamWriter - with anything that connects to a
file - is you've got to close the file afterwards. And that's all handled for you by the fact that
StreamWriter, which is derived from TextWriter, but TextWriter implements IDisposable, therefore
has the Dispose method; and that is what does the shutdown - the disconnection from the file.
So if we were using StreamWriter directly, we'd put it in a 'using' block, but because we're
using it inside a class - our PureManagedClass, which is itself IDisposable. So on the Dispose
there I just pop out a message - again purely for debugging purposes - but then
we call Dispose on the '_writer' using the question mark just to check that it's
been created at all, because we didn't necessarily have to call StartWriting. And so now that simply
means that whoever uses PureManagedClass has to make sure that they call Dispose on that, and this
should all be okay. So the way that would happen here, we've got the program, so I've got a Main
calling Run, and then I do this garbage collect just so we'll force a garbage collection at that
point, not that it actually matters for Dispose, but it's going to matter later on. And then
what I'm going to do is say 'using' and then 'PureManagedClass' and let's just call that
'pmc = new' and all of that. And then let's say 'pmc.StartWriting();' So that will create the
StreamWriter, and then we'll see when we run that that it says 'Disposing' because when it left
the block in which it was declared as 'using' then when it went through that curly brace, it
did the Dispose. And so that's a pretty safe way to go. But the problem is, of course, that even
in our class, if we do all the right things like we've done here, if the client code forgets
to do the 'using' block then we don't get the Dispose called, and so we don't get correct tidy-up.
And the really important thing about that is we're in complete control of when that happens, so
by putting in the 'using' we know that it's going to be as soon as we've finished with the class
it's going to be disposed - we're going to get rid of those resources that we don't need anymore,
like the connection to the file. But if we forget, then we can see we've got problems. And that's why
we have a finalizer, because what I can also do if we've forgotten to do that, but I could put in
the finalizer here, which has the syntax '~' and then the name of the class. So this actually comes
from C++ originally, but it's intended to be the opposite of the constructor effectively. And then
all I'm going to do in here, for now, is just put the message so we can see what's happening,
so 'Finalizing'. And so now when we run that, we can see it says 'Finalizing', and that happened
when we went through here. It's worth noting a slight subtle difference between .NET Framework
and .NET Core. In .NET Framework, when the program terminated then any finalizers got called,
whereas in Core, although they get called when we explicitly ask for it or when a garbage collect
happens, it doesn't happen on termination. So that's why I need that in there as that last line.
So we can see that's working, but that then hits us with another problem, which is, well, what
now if the user does remember to put this in a 'using' block, and we'll see now that we get both
the 'Disposing' and the 'Finalizing' - and that's something we really don't want to happen for two
reasons. One is it could simply be inefficient doing the same thing twice; once you've disposed
you don't need to finalize. But additionally, it could also break things because if we try
to close the file and then try to close it again, then that could give us an error. And so
another thing we can do is inside a Dispose we can, having done whatever's necessary,
we can say 'GC.SuppressFinalize' on ourself. And so that means although we've got a
finalizer, it won't be called because we've suppressed it. And so now we can see if
we run that now, we just get the 'Disposing', whereas if we miss off the 'using' we just get
the 'Finalizing'. So that is the safe way to go. It's also worth mentioning, apart from the
problem of doing it twice and that sort of thing, finalizing is inherently an inefficient thing
to do. If you remember what was happening with the garbage collection when we looked at last
time, once all the objects that are not being collected have been marked and moved on to the
next generation heap then the remaining objects don't have to have anything explicit done to them.
The heap pointer just drops down to the bottom of the heap and any new allocations just right
over the existing objects. Well, you can't do that if you're finalizing, because those objects
can't just be left there. They've also got to be finalized. So they're put into a thing called
the 'finalizer queue' which then gets processed; not horribly inefficient, but even if you had
a completely empty finalizer - so simply by writing it in there, even if it had nothing at all
- then that would slow things down. So that's why it's better if you don't need it, not to have a
finalizer. But it's also another reason why if you do need a finalizer then when you're not going to
need it, get rid of it with 'SuppressFinalize();' That said though, in a class like this the
finalizer isn't really necessary at all. Notice I haven't duplicated that code and put
the Dispose in there, and indeed if I do, we get this rather unpleasant error and the reason
for that is this. If we just consider this '_writer' - this object of the class StreamWriter
- in the finalizer we're in the middle of the process of garbage collection. And so as well
as our object of PureManagedClass being garbage collected, the StreamWriter itself is also being
garbage collected. And there is nothing we can do whatsoever to determine or manage the order in
which that happens. Both of these objects will have been unmarked because they're unreachable,
therefore the garbage collector deals with them and it may already have disposed of the
StreamWriter, and therefore we don't need to and we should not dispose of it for ourselves.
So that's why I've highlighted the fact this is a pure managed class, because if you're entirely in
the managed environment - if everything you've got is subject to garbage collection - then you
never need finalizers at all. So we can simply remove that because it's just not something
we're going to have. When, then, do we need to worry about finalization? Well, we need to
worry about it if we are dealing with unmanaged classes. Now this is something that in the early
days of .NET you did lots and lots of because there was lots of legacy code you had to access.
But it becomes very less frequent these days. But it will still sometimes happen, and so what
I've got here is a 'MixedClass', and this, as well as having the StreamWriter which is
managed - therefore garbage collected, therefore we don't need to worry about - we've also got this
COM Interop. So this is a way of connecting to Excel - in this case - or any Office application,
or indeed all sorts of other applications. So just look at how I've done that. I've added a reference
to this Interop in there and then I can make use of that in here. But the thing about it is, it
is unmanaged. Therefore I've got to be much more careful about what's going on. So let's put that
into the program. So let's have this 'MixedClass mc' - still got a method called 'StartWriting' so
the same sort of thing that creates both of the objects. And so the thing we've got to do is,
if we have a Dispose that's got to get rid of both the StreamWriter - which is managed - and the
Excel object - which is unmanaged. But if we have a finalize, we only need to get rid of the Excel
object, because the StreamWriter will already have been garbage collected. And that brings us on
to the all-singing all-dancing solution to this, which is the Dispose Pattern. Because if we go in
here, and if I put onto MixedClass the IDisposable interface - get hold of the namespace -
and then when you go to the light bulb, you'll see that this actually gives us not just
the ability to implement the interface - which would just write in the Dispose method - but
we've got this 'Implement Interface with the Dispose Pattern'. And if I click on that, it puts
in a huge amount of code that is really there to deal with any circumstances. So what it gives
us is this implementation of Dispose - so that's the actual Dispose method from the IDisposable
interface - but then it also gives us this overload of Dispose which is virtual and
takes this boolean called 'disposing'. So what that boolean means is, if this is the Dispose
method being called at the end of a using block then that will be true, whereas if this
is finalization during garbage collection then that will be false. And so we can see in our
actual Dispose method we call the other Dispose, setting that to 'true' - so saying this is
disposal. And then as we saw a little while earlier, we suppress the finalize so that will be
the normal Dispose. But then we also - if we need to, and we do in this case - have the finalizer,
and in that case we call Dispose passing in that disposing flag as 'false'. Now you could do this
in other ways, but it just makes life easier if we centralize both behaviours - for finalization
and Dispose - through this one method. We then have just this flag 'disposedValue' just to stop
us doing it twice. So we check that that's not true and then at the end of it all we set
it to true. Then we have 'if (disposing)', so this is the stuff we want to do only
when it's a genuine Dispose - and remember that is tidying up the StreamWriter - the
managed object. So here is where we say '_writer?.Dispose();' So what we had before.
So basically, in the simple case on the managed class, that's all we really need to do. But
then we can see that what we then do here is we tidy up the unmanaged code. So this is
what wants to happen both when we're disposing and when we're finalizing. And so what I'll
do here - the details don't matter too much, it's not really what this video is about - but
I'm going to say 'if (_excel != null) just in case it hasn't been created, and then I'm going
to say '_excel.Quit()', and then I'm going to say 'Marshall' - get hold of the namespace for that
- and then 'ReleaseComObject' on'_ excel'. So that should do the tidy up for us. And you'll
notice it's giving us a warning there, and what that warning is telling us is, because this is
.NET 5, which can run on multiple platforms, it doesn't like the fact that this actually is a
Windows-only feature. So we can get around that by putting on this 'SupportedOSPlatform' - and again
get another namespace for that - and just specify that this method will only work on 'windows'.
And then for consistency, we need to put that on these other methods as well. So that's the
full-blown version. So that means if it Disposes, it will get rid of the '_writer' and then also get
rid of the COM object, whereas if it's finalizing, it will just get rid of the COM object. Let's just
put some code in there so we can see what's going on. So here we'll put 'Disposing of writer'
and then in here we'll put 'Releasing excel'. And then back in our program, if we remember
to do this in the 'using' block and we run it, then we'll see 'Disposing of
writer', 'Releasing excel' that happened in the dispose when we left
the using block. Whereas if we miss it off then it just says 'Releasing excel' because
we don't need to Dispose the writer, because that's already going to happen because it's a
managed object as part of garbage collection. So that's the full-blown Dispose Pattern,
and you can see it's only necessary if we're dealing with unmanaged objects, in the
case of our Excel Interop there. If you're in a purely managed environment, completely forget
about finalizers, forget therefore about the Dispose Pattern and simply just use the Dispose
method as a direct implementation. That said, there is one thing to worry about which
is, well, hang on; if we write a class it's okay for us to say when we write a class that
it doesn't have any unmanaged content. But what if somebody derived from that class and therefore
in the derived class starts having unmanaged content? And in fact that's why, if you look at
one minor detail of this, this Dispose method is declared as 'virtual'. So the idea is that
even if we didn't have anything of our own to do, a derived class that had to worry about
unmanaged code could still override this and that would work fine. And so that's
why in a really purist way of doing things, it would be regarded here for example, as this is
not quite the right thing to do. Because although our PureManagedClass doesn't need any complexity
- doesn't need a finalizer - it could be derived from, and therefore we should have the Dispose
Pattern just for that potential problem. And some code checkers will object to this. Obviously if
you know what's happening in your code, then it's not something to worry about. But if there is
just this possibility then actually the easiest thing to do is if we make that class 'sealed' then
we know no one's going to derive from, it so no unmanaged code can be introduced. So that was just
the final piece related to garbage collection: the use of finalizers. And it is extremely rare
nowadays, because use of non-managed classes is rare. But if you have got unmanaged classes,
that's when you need to start thinking about adding a finalizer. And probably best done using
some variant of that Dispose Pattern. But if not, don't go overboard; just have a simple Dispose -
that's how you deal with other managed objects. So I hope that was helpful. Next time we're going
to be looking at something completely different, but if you enjoyed that, do click the like
button. Do subscribe and I'll see you next time.