In this post, I will be discussing 3 topics by which we can clean up our mess created in our programs provided in C# .NET world.
Bit History
I am sure you must be thinking now that “oh why re-inventing wheel?” or “Oh no not another same article which is available everywhere”. Honestly, these days I wanted to gain in depth knowledge on these topics and hence 3-4 days ago, I started reading many articles/blogs online in my free time. Upon reading so, I got many doubts which I started to ask myself. And I could not find answers for those self questions in one article/blog online. The information was so scattered, from Codeproject to Stackoverflow to CodeBetter to Codeguru and to couple of blogs.
Luckily after spending a long time reading all the articles and trying to figure out the answers, I thought about writing a blog post on the main learning points. I shall be explaining in detail about these topics and in the next couple of posts as and when I dig more and more into it. I would not claim that the content in this article is extraordinary because I might have missed out an article which could be good. It's just that I could not find the information easily. I have tried my best to give as much in depth information as possible. So kindly leave a comment if I have left out anything or interpreted something wrongly.
Let me start off then.
Most of the C# .NET developers do get confused about these 3 topics and I must say here that these topics are really important as you gain higher programming experience in the C# .NET world.
Note: In this article, I shall use Dtor. for Destructors and Ctor. for Constructors.
Introduction
We know in the programming world, whenever we write something or consume something in programs, we are occupying some space which is a memory here. So it's always been our headache for decades in the programming world to clean up our mess, the reason I call it as mess is that no matter how carefully we write our code, still there are many areas we might leave our footprints which we fail to clean up.
So traditionally all these years, most of our smart developers in various native languages viz C, C++, Pascal, etc. used to write a lot of code to ensure that the memory is cleaned up neatly though it may not be the perfect ones but atleast we can say some cleaning up was better than making it more messy or left undone. It was not easy to write efficient code to clean up our used resources. So you can think how memory management was most complex and worried topic.
To avoid heavy complexity in managing memory, .NET technology came as a boon to developers who used to spend day and nights for memory management earlier. In .NET, memory management in cleaning up is often taken care off by our beloved GC, yes we all know that, so nothing new. But we often fail to convince ourselves that just because GC is there, it is not yet perfect. Because after all, it's again a bunch of codes written with some AI (Artificial intelligence) put into it in taking care of memory efficiently.
Even though GC does most of the jobs in reclaiming the memory back consumed by our code, still there are times we need to make sure we explicitly write code to release memory or help GC in its job. Hence to do this, C# .NET provided us 3 ways. So let's dig deep into each of those ways:
Destructors
This is not a feature which .NET supports but as a language, C# has derived it from its predecessor C++ on which this language evolved.
“Don’t be surprised if you read or hear from somebody saying that .NET does not support destructors.”
Let's get into detail about the Dtor., then:
Like C++, the signature for Dtor. is similar in C# as well. So as per that C# like C++, Dtor. has the same name as the class prefixed with ~ char. Unlike Ctor., Dtor. can’t have any Access Modifiers associated with it nor can you make an explicit call. This is because this is the last method which shall be called automatically by the run time before the resource is getting actually cleaned up or marked for garbage collection and hence nobody else should be allowed to call explicitly, hence in IL a family (protected) modifier is assigned to this method to make it available for subclass, so that run time can track dependencies down the object hierarchy.
Now let's look a bit deeper into Dtor. here, upon disassembling, below compiled code in IL the Dtor. has been changed to something else as seen in the below image:
class MyClass
{
~MyClass() { }
}
As you can see from the above image, the IL shows that the Dtor. method has been converted to Finalize()
method, well this is how the Run time identifies and calls it before really the object is garbage collected. Hence I said before that Dtor. is not a feature .NET is offering to us but rather C# is. This way, this object can be marked for finalization. Don’t get worried about Finalization now, I shall explain later.
Let's see what's inside this strange named method called Finalize()
aka the Dtor.
As you can see from the above image, the compiler has introduced try
-finally
block into the Dtor. method.
The compiler is making sure that no user/developer written code in the Dtor. throws exception or go unhandled, the reason being that in no case the memory reclaiming process should face any exception getting raised, i.e., the GC expects that anything on which it is reclaiming the memory should not throw any exceptions.
A point worth mentioning here is that, even though upon any exceptions generated on an instance, which GC is reclaiming memory by finalizing it. GC shall ignore those exceptions which got generated but at the same time, it stops reclaiming memory. So it's very uncertain when the left incomplete job is taken up again will be completed.
Now there is one strange method being called inside finally
block if you look at the IL picture. This is because it is explicitly notifying the GC to garbage collect this object. I shall talk about Finalize()
method later.
Let's ask ourselves the following questions about Dtor here and see if we can answer it convincingly to ourselves first.
- Why is that I see a
Try
-Catch
/Finally
block in the transformed Dtor method?
As already explained to make sure and to safe guard for a clean memory reclaim, the compiler goes for a defensive coding and applies those blocks around your code.
- Can I fool the compiler by providing a custom
Try
-Catch
/Finally
block in the code?
Even though you can provide your custom blocks in your Dtor. code, still the compiler ensures to apply this defensive blocks after compilation. This is because, even your catch
/finally
block which you have provided can still throw an exceptions.
- I see
Object.Finalize()
method being called in Finally
. Why?
As already said, .NET only provides Finalizers to us and not Dtor. Dtor. feature is only by C# as language to facilitate Finalization. Although I shall talk more about Finalization later, to outline/answer this question, Object.Finalize()
notifes the GC to garbage collect this object. But don't be relaxed reading this, doing finalization is not as easy as you think by GC. Hence, in .NET, GC is non-deterministic in nature. Hence, it has to be called at the end of your object life cycle.
- Why can't I directly call this
Finalize()
method rather than using Dtor.?
Finalize()
method is a protected
method of Object
class and C# does not allow you to call that method. Try it and you get a compiler error. So it's a trick that the compiler is playing. The reason is that calling Finalize()
is not recommended because it deals with a lot of complex operations internally. If allowed to call it explicitly in C#, a developer can call it whenever and at any time he/she wants. This will overload the whole system rather than improving it. Hence to avoid it, C# does ask you to use Dtor.
- Since I can’t call
Finalize()
, it should have been private
but why it is protected
?
The answer is simple, in the .NET world, every instance is a descendant of System.Object
right? So, if Finalize()
is private
, then Finalization of that object is not possible and every child object has to provide its own implementation of Finalize
if this was true
. This adds a lot of overhead. To reduce all this craziness, it's made protected
and C# does not allow you to use it explicitly.
A point worth mentioning here is that, even if you wish to see Object.Finalize()
method via Visual Studio's Object Browser, then you can’t. You can only see ~Object()
method which is kind of an alias for Object.Finalize()
as you can see from the below image:
The same holds true for System.object
source code in Visual Studio too. Although the Objects' Dtor. is also missing here as you can see from below image:
So if you are really curious like me to see if there’s really a Finalize()
method in System.Object
class, then disassembling is the only way as shown below:
That’s all, I do hope you have got enough insight into Dtor. features in C#.NET.
In the next post, I might continue on Finalizers and finally on Dispose pattern in depth if I can find something interesting about it from my experiments.
Thanks for reading!
P.S.: Your valuable comments/votes are well appreciated.