C# 基本程序中的析构函数不起作用(输出缺失)

Destructor in C# basic program does not work (output missing)(C# 基本程序中的析构函数不起作用(输出缺失))

本文介绍了C# 基本程序中的析构函数不起作用(输出缺失)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经编写了下面的非常基本的程序,我是 C# 新手.析构函数 ~Program() 没有被调用,所以我在输出中看不到调用析构函数"字符串.我已经检查了其他类似的问题,但我没有找到我的答案.谢谢.

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");
        }

        }

    }

推荐答案

简短的回答——你没有看到Destructor called"输出的原因——被埋在评论的某个地方:

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

.NET Core 不会在程序结束时运行终结器

(请参阅:终结者(C# 编程指南)).

.NET Framework 会尝试这样做,但 .NET Core 不会这样做.

.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.

不过,根据 Raymond Chen 在他的帖子中 每个人都以错误的方式思考垃圾收集,如果 .NET Framework 没有在程序结束时运行终结器,它也不会无效.相关的引用,从不同的角度说,是这样的:

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.

在进一步使用 C# 之前,您将不得不放弃 .NET 中的析构函数,因为它们根本不存在.C# 使用 C++ 的析构函数 syntax 作为终结器,但相似之处仅止于此.

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.

我想说,与 C++ 析构函数相比,您将看到的主要区别是:

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

  1. 类的实现者还必须支持一次性模式.
  2. 该类的使用者还必须使用 using 语句来选择加入.
  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.

如果您想了解更多有关如何操作的信息,请继续阅读...

在我进入任何代码之前,有一点值得提一下:

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

  • 终结器绝不能为空.它会导致实例更长时间地保持活动状态.
  • 正如 mjwills 在评论中所说,在 99.9% 的情况下,您不应该编写终结器.如果您发现自己正在编写一个,请退后一步,确保您有充分的理由使用 .NET 代码,而不是因为您会在 C++ 中这样做.
  • 通常情况下,您将在派生自实现一次性模式的类之后覆盖 Dispose(bool),而不是创建需要一次性的类层次结构的基础.例如,Windows Forms 应用程序中的 .Designer.cs 文件会覆盖 Dispose(bool) 以便处理 components 字段(如果不是)null.
  • 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.

好的,代码...

以下是一个实现一次性模式的简单类的示例.它不提供对子类的支持,因此它被标记为 sealed 并且 Dispose(bool)private.

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
    }
}

实际清理发生在 Dispose(bool) 方法中.如果参数为true,则表示正在通过IDisposable接口进行处理(通常是using语句,但不一定),可以清理托管资源.如果它是 false,则表示处置是作为 GC 扫描的一部分进行的,因此您无法触及托管资源,因为它们可能已经被收集.

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.

如果您正在编写一个需要支持一次性模式的基类,情况会发生轻微变化:Dispose(bool) 变为 protectedvirtual 所以它可以被子类覆盖,但消费者仍然无法访问.

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
    }
}

然后以下是使用该支持的子类.子类也不需要实现终结器或 IDisposable.Dispose.他们所要做的就是重写Dispose(bool),释放自己的资源,然后调用基础实现.

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?

托管资源类似于其他一次性对象,甚至是非一次性对象(例如字符串).BCL 中的一些一次性类型会将这些字段设置为 null 以确保 GC 不会找到对它们的活动引用.

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.

非托管资源是诸如文件句柄、全局内存、内核对象...几乎是您通过调用操作系统分配的任何东西.它们不受垃圾收集器的影响,无论如何都需要释放,因此它们不受 disposing 测试.

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.

如果您的一次性对象使用了另一个具有非托管资源的一次性对象,则该对象有责任实现 Disposable 模式以释放其资源,而您有责任使用它.

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.

并非所有实现 IDisposable 的对象实际上都具有非托管资源.通常,基类支持一次性模式仅仅是因为它的作者知道从它派生的至少一个类可能需要使用非托管资源.但是,如果一个类没有实现一次性模式,它的子类之一可以在需要时引入该支持.

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.

注意:据我所知,Main 创建包含 Program 类的实例并不常见.我这样做是为了尽可能地接近原作.

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");
    }
}

针对 .NET Framework 4.7.2 构建并运行,您将获得以下输出:

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

针对 .NET Core 2.1 构建并运行,您将获得以下输出:

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

实例 p1p2 由于 using 语句以及托管和非托管语句,它们的构造顺序与它们的构造相反资源被释放.这是试图使用析构函数"背后的期望行为.

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".

另一方面,.NET Framework 和 .NET Core 在最后做的事情有点不同,显示了我在开头提到的差异:

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 的 GC 调用了 p0 的终结器,因此它只释放了非托管资源.
  • .NET Core 的 GC 没有为 p0 调用终结器.
  • .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.

这篇关于C# 基本程序中的析构函数不起作用(输出缺失)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:C# 基本程序中的析构函数不起作用(输出缺失)