The Dispose Pattern

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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.
Info
Channel: Coding Tutorials
Views: 620
Rating: undefined out of 5
Keywords: .net, .net core, dispose, IDisposable, finalizer, c#, dispose pattern
Id: CV5UlcfFls4
Channel Id: undefined
Length: 16min 28sec (988 seconds)
Published: Fri Mar 26 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.