Destructor in C# basic program does not work (outp

2020-03-31 03:29发布

问题:

I have written the very basic program below, I am new to C#. The destructor ~Program() doesn't get called, so I do not see in the output the 'Destructor called' string. I have checked other similar questions but I don't find the answer to mine. Thanks.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using static System.Console;

namespace LyfeCicleObject
{
    class Program
    {
        public Program()
        {
            WriteLine("Cons called");
        }

        ~Program()
        {
            WriteLine("Destructor called");           
        }

        static void Main(string[] args)
        {
            WriteLine("Main started");

            Program p1 = new Program();
            {
                WriteLine("Block started");
                Program p2 = new Program();
                WriteLine("Block ended");
            }

            WriteLine("Main ended");
        }

        }

    }

回答1:

The short answer -- the reason you're not seeing the "Destructor called" output -- was buried somewhere in the comments:

.NET Core does not run finalizers at the end of the program

(See: Finalizers (C# Programming Guide)).

.NET Framework will attempt to do it but .NET Core just won't do it.

Disclaimer: We have no way of knowing if these statements will continue to hold true; this is how they're implemented and documented as of now.

According to Raymond Chen, though, in his post Everybody thinks about garbage collection the wrong way, it would not be invalid if .NET Framework didn't run finalizers at the end of the program, either. The relevant quote, which says it from a different perspective, is this:

A correctly-written program cannot assume that finalizers will ever run.

So as long as you don't assume that finalizers will run, it shouldn't matter how they're implemented or if an implementation changes.

Before going any further with C#, you're going to have to abandon the idea of destructors in .NET because they simply don't exist. C# uses C++'s destructor syntax for finalizers but the similarities stop there.

The good news is that there is a way to do something close to what you were trying to do, but it takes a paradigm shift, a substantial change in how you think about resource acquisition and release. Whether or not you really need to do it is a totally different question.

Finalizers aren't the only way, or even the best way, to release resources that need to be released in a timely manner. We have the disposable pattern to help with that.

The disposable pattern allows class implementors to opt in to a common mechanism for deterministically releasing resources (not including memory on the managed heap). It includes finalizers but only as a last chance at cleanup if the object wasn't disposed properly, especially if the process isn't terminating.

I'd say the main differences you'll see compared to C++ destructors are:

  1. There's more that the class's implementor must do support the disposable pattern.
  2. The class's consumer also has to opt in to it with a using statement.

What you won't see is that the memory won't necessarily be reclaimed immediately.


If you want to know more about how, read on...

Before I get into any code, it's worth mentioning some points of caution:

  • A finalizer should never be empty. It causes instances to be kept alive longer and for nothing.
  • As mjwills stated in a comment, 99.9% of the time, you shouldn't be writing a finalizer. If you find yourself writing one, take a step back and make sure you have a good reason to do it in terms of .NET code and not because you would do it that way in C++.
  • More often than not, you'll be overriding Dispose(bool) after deriving from a class that implements the disposable pattern rather than creating the base of a class hierarchy that needs to be disposable. For example, the .Designer.cs files in Windows Forms applications override Dispose(bool) in order to dispose of the components field if it's not null.

Okay, code...

The following is an example of a simple class that implements the disposable pattern. It doesn't provide support for child classes so it's marked sealed and Dispose(bool) is private.

public sealed class SimpleDisposable : IDisposable
{
    public SimpleDisposable()
    {
        // acquire resources
    }

    ~SimpleDisposable()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        // Suppress calling the finalizer because resources will have been released by then.
        GC.SuppressFinalize(this);
        Dispose(true);
    }

    private void Dispose(bool disposing)
    {
        if (disposing)
        {
            // release managed resources
            // (you don't want to do this when calling from the finalizer because the GC may have already finalized and collected them)
        }

        // release unmanaged resources
    }
}

Actual cleanup takes place in the Dispose(bool) method. If the parameter is true, it means that disposal is happening via the IDisposable interface (usually a using statement but not necessarily) and it's okay to clean up managed resources as well. If it's false, it means that disposal is happening as part of a GC sweep, so you can't touch managed resources because they may have already been collected.

If you're writing a base class that needs to support the disposable pattern, things change slightly: Dispose(bool) is becomes protected and virtual so it can be overridden by subclasses but is still inaccessible to the consumer.

The following is an example of a base class that supports the disposable pattern for subclasses.

public abstract class BaseDisposable : IDisposable
{
    protected BaseDisposable()
    {
        // acquire resources
    }

    ~BaseDisposable()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        GC.SuppressFinalize(this);
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // release managed resources
        }

        // release unmanaged resoures
    }
}

And then the following is a subclass that uses that support. Subclasses don't also need to implement the finalizer or IDisposable.Dispose. All they have to do is override Dispose(bool), dispose of their own resources, and then call the base implementation.

public class DerivedDisposable : BaseDisposable
{
    public DerivedDisposable()
    {
        // acquire resources for DerivedDisposable
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // release DerivedDisposable's managed resources
        }

        // release DerivedDisposable's unmanaged resources

        // Let the base class do its thing
        base.Dispose(disposing);
    }
}

So what does it mean to dispose managed and unmanaged resources?

Managed resources are things like other disposable objects and even non-disposable objects (e.g. strings). Some disposable types in the BCL will set such fields to null to ensure that the GC doesn't find active references to them.

When your class is being disposed, the consumer has decided that it and its resources are no longer needed. If your object contains other disposables, it's okay to dispose those objects, and so on down the chain, because it's not happening during garbage collection.

Unmanaged resources are things like file handles, global memory, kernel objects... pretty much anything you allocated by making a call to the operating system. Those aren't affected by the garbage collector and need to be released no matter what, so they're not subject to the disposing test.

If your disposable object used another disposable object that has an unmanaged resource, it's that object's responsibility to implement the Disposable pattern to release its resources, and your responsibility to use it.

Not all objects that implement IDisposable actually have unmanaged resources. Often a base class will support the disposable pattern simply because its author knows that at least one class that derives from it might need to use unmanaged resources. However, if a class doesn't implement the disposable pattern, one of its subclasses can introduce that support if it needs it.


Let's alter your program a bit and make it do what you were expecting, but using the disposable pattern now.

Note: As far as I know, it's not very common to have Main create an instance of the containing Program class. I'm doing it here to keep things as close to the original as possible.

using System;

internal sealed class Program : IDisposable
{
    private readonly string _instanceName;

    public Program(string instanceName)
    {
        _instanceName = instanceName;

        Console.WriteLine($"Initializing the '{_instanceName}' instance");
    }

    ~Program()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        GC.SuppressFinalize(this);
        Dispose(true);
    }

    private void Dispose(bool disposing)
    {
        if (disposing)
        {
            Console.WriteLine($"Releasing the '{_instanceName}' instance's managed resources");
        }

        Console.WriteLine($"Releasing the '{_instanceName}' instance's unmanaged resources");
    }

    private static void Main(string[] args)
    {
        Console.WriteLine("Main started");

        Program p0 = new Program(nameof(p0));
        using (Program p1 = new Program(nameof(p1)))
        {
            Console.WriteLine("Outer using block started");
            using (Program p2 = new Program(nameof(p2)))
            {
                Console.WriteLine("Inner using block started");
                Console.WriteLine("Inner using block ended");
            }
            Console.WriteLine("Outer using block ended");
        }

        Console.WriteLine("Main ended");
    }
}

Build and run against .NET Framework 4.7.2 and you get the following output:

Main started
Initializing the 'p0' instance
Initializing the 'p1' instance
Outer using block started
Initializing the 'p2' instance
Inner using block started
Inner using block ended
Releasing the 'p2' instance's managed resources
Releasing the 'p2' instance's unmanaged resources
Outer using block ended
Releasing the 'p1' instance's managed resources
Releasing the 'p1' instance's unmanaged resources
Main ended
Releasing the 'p0' instance's unmanaged resources

Build and run against .NET Core 2.1 and you get the following output:

Main started
Initializing the 'p0' instance
Initializing the 'p1' instance
Outer using block started
Initializing the 'p2' instance
Inner using block started
Inner using block ended
Releasing the 'p2' instance's managed resources
Releasing the 'p2' instance's unmanaged resources
Outer using block ended
Releasing the 'p1' instance's managed resources
Releasing the 'p1' instance's unmanaged resources
Main ended

Instances p1 and p2 were disposed in the reverse order in which they were constructed because of the using statements, and both managed and unmanaged resources were released. This was the desired behavior behind trying to use a "destructor".

On the other hand, .NET Framework and .NET Core did things a bit different at the end, showing the differences I mentioned at the beginning:

  • .NET Framework's GC called the finalizer for p0 so it only released the unmanaged resources.
  • .NET Core's GC did not call the finalizer for p0.


回答2:

If you want to see the output from finalizer:

  • add new method
  • initialize variable p1 inside
  • add GC.Collect()
  • call new method from Main
  • profit

If you want to know the reason of current behavior: the p1 variable exists in the scope of the Main method and will be collected by the GC when scope gets unreachable and I suppose it happens when program already stopped that means no GC (memory will be released in another way).