I have this simple code and trying to call the destructor but I can\'t call it :(
I know that GarbageCollector runs when it\'s necessary, so I used GC.WaitForPending
If you run a program with the debugger attached it changes the behavior of the lifetime of objects.
Without the debugger a object becomes ellagable for collection as soon as the last use of the object has been passed in the code. With the debugger attached the lifetime of all objects get extended to the entire time the object is in scope, this is done so you can view the object in the Watch
window of the debugger and not have the object collected out from under you.
You must either run your program in release mode without the debugger attached or set calculator
to null
before you call GC.Collect()
to be able to have the object be eligible for garbage collection and have it's finalizer run.
I would not recommend to really on destructors .net
anyway in your case GC don't think your object is garbage at the moment you calling GS because you have alive link in your stack calculator which is point to object in heap so you can try to modify this code
main(){
DoCalculations();
//at this point object calculator is garbage (because it was allocated in stack)
GC.Collect();
}
DoCalculations(){
Calculator calculator = new Calculator(); // object allocated
calcualtor.doSomething(); //link alive
}
The lifetime of a local variable is the lifetime of the activation of control within the local variable scope that declares it. So your local is alive until the end of main. That alone is sufficient to explain why it is not collected, but there are subtleties here that we should explore in more depth.
The lifetime may be extended by a variety of mechanisms, including capturing outer variables by a lambda, iterator blocks, asynchronous methods, and so on.
The lifetime is permitted to be shortened in cases where the jitter can prove that doing so has no effect on the single-threaded flow of control. (You can use KeepAlive
to ensure this shortening does not happen in cases where you must avoid it.)
In your case, the runtime is permitted to notice that the local is never read from again, mark it as dead early, and thereby orphaning the reference to the object, which would then be collected and finalized. It is not required to do so, and apparently, in your case, does not.
As another answer correctly notes: the GC will deliberately suppress this optimization if it detects that a debugger is running, because it is a bad user experience for an object to be collected while you are examining a variable containing a reference to it in the debugger!
Let's consider the implications of my statements about shortened lifetimes, because I think you may not have fully grasped those implications.
The runtime is permitted to notice that the ctor never accesses this.
The runtime is permitted to notice that divide never accesses this.
The runtime is permitted to notice that therefore the local is never actually read from and used
Therefore the object is permitted to be never rooted in the GC at any point in its lifetime.
Which means that the garbage collector is permitted to run the finalizer before the constructor.
The GC and finalizer runs on their own threads, remember; the operating system could suspend the main thread and switch to the gc and finalizer threads at any point, including after the allocator runs but before control passes to the constructor.
Absolutely crazy things are permitted to happen in scenarios like the one you wrote; the finalizer not running is the least of your problems! It is when it could run that is scary.
If that fact was not immediately clear to you, then you have no business writing a finalizer. Writing a correct finalizer is one of the hardest things to do in C#. If you are not an expert on all the fine details of the CLR garbage collector semantics, you should not be writing a finalizer.
For more thoughts on how writing a finalizer is difficult, see my series of articles on the subject, which begins here:
https://ericlippert.com/2015/05/18/when-everything-you-know-is-wrong-part-one/