你了解 C# 语言中的析构函数吗?

作者:微信公众号:【架构师老卢】
7-19 8:41
38

概述:析构函数是一种特殊类型的方法,在对象被破坏时被调用。它们用于释放对象可能持有的未托管资源,例如文件句柄、数据库连接或未托管的内存。虽然 C# 中的垃圾回收器 (GC) 会自动管理托管对象的内存,但析构函数对于显式清理未托管的资源至关重要。在这篇文章中,我们将讨论什么是析构函数,它们是如何工作的,以及何时使用它们。我们还将讨论在 C#/.NET 应用程序中实现析构函数的最佳实践。什么是析构函数?C# 中的析构函数是一种方法,当不再需要某个对象并由垃圾回收器收集时,该方法将自动调用。它允许对象在被销毁之前清理任何资源。析构函数类似于其他编程语言中的终结器。析构函数的语法析构函数的语法很简单。它使用

析构函数是一种特殊类型的方法,在对象被破坏时被调用。它们用于释放对象可能持有的未托管资源,例如文件句柄、数据库连接或未托管的内存。虽然 C# 中的垃圾回收器 (GC) 会自动管理托管对象的内存,但析构函数对于显式清理未托管的资源至关重要。

在这篇文章中,我们将讨论什么是析构函数,它们是如何工作的,以及何时使用它们。我们还将讨论在 C#/.NET 应用程序中实现析构函数的最佳实践。

什么是析构函数?

C# 中的析构函数是一种方法,当不再需要某个对象并由垃圾回收器收集时,该方法将自动调用。它允许对象在被销毁之前清理任何资源。析构函数类似于其他编程语言中的终结器。

析构函数的语法

析构函数的语法很简单。它使用波形符 () 后跟类名进行定义。析构函数不接受参数,也不返回任何值。~

class Student  
{  
    // Constructor  
    public Student()  
    {  
        // Initialization code  
    }  
  
    // Destructor  
    ~Student()  
    {  
        // Cleanup code  
    }  
}

在此代码中, 是类的析构函数。

析构函数的工作原理

当垃圾回收器确定无法再访问某个对象时,它会在回收对象使用的内存之前调用该对象的析构函数(如果已定义)。此过程是非确定性的,这意味着您无法准确预测何时调用析构函数。这取决于 GC 的日程安排。

垃圾回收器的作用

C# 依赖于垃圾回收器 (GC) 来管理内存分配和解除分配。当一个对象不再可访问时,GC 会将其视为收集对象,并最终调用其析构函数(如果有)。

此过程涉及:

  1. 标记:GC 标记仍在使用的所有对象。
  2. 扫描:然后,它扫描堆,识别未标记的对象并恢复其内存。
  3. 完成: 如果对象具有析构函数,则 GC 会将其添加到终止队列中,在该队列中将调用析构函数,然后再回收内存。

何时使用析构函数

虽然析构函数可能很方便,但它们的使用应仅限于特定方案,主要用于释放未托管的资源。非托管资源是 .NET 运行时不直接处理的资源,例如文件句柄、数据库连接或网络套接字。

对于托管资源,最好实现接口和方法。该方法允许确定性资源清理,使您可以控制何时释放资源。

实现 IDisposable 以实现确定性清理

下面是实现接口的类的示例:

using System;  
using System.IO;  
  
class FileHandler : IDisposable  
{  
    private FileStream fileStream;  
    private StreamReader reader;  
    private bool disposed = false;  
  
    // Constructor  
    public FileHandler(string filePath)  
    {  
        fileStream = new FileStream(filePath, FileMode.Open);  
        reader = new StreamReader(fileStream);  
    }  
  
    // Method to read a line from the file  
    public string ReadLine()  
    {  
        if (disposed)  
            throw new ObjectDisposedException("FileHandler");  
  
        return reader.ReadLine();  
    }  
  
    // Destructor  
    ~FileHandler()  
    {  
        Dispose(false);  
    }  
  
    // Public implementation of Dispose pattern callable by consumers  
    public void Dispose()  
    {  
        Dispose(true);  
        GC.SuppressFinalize(this);  
    }  
  
    // Protected implementation of Dispose pattern  
    protected virtual void Dispose(bool disposing)  
    {  
        if (!disposed)  
        {  
            if (disposing)  
            {  
                // Free any other managed objects here.  
                if (reader != null)  
                {  
                    reader.Close();  
                    reader.Dispose();  
                }  
  
                if (fileStream != null)  
                {  
                    fileStream.Close();  
                    fileStream.Dispose();  
                }  
            }  
  
            // Free any unmanaged objects here, if any.  
            disposed = true;  
        }  
    }  
}

解释

  1. 构造函数:构造函数初始化 and 以从文件中读取。
  2. ReadLine 方法:一种从文件中读取行的方法。它检查对象是否已被释放,如果已释放,则引发异常。
  3. 析构函数:析构函数 () 调用 ,确保在对象进行垃圾回收时清理资源。
  4. Dispose 方法:使用者显式调用该方法以释放资源。它调用并禁止终结。
  5. Dispose(bool disposing) 方法:此方法执行实际清理。如果为 is,则释放托管资源(文件流和读取器)。它还设置了标志以防止多次处置。

以下是在应用程序中使用该类的方法:

class Program  
{  
    static void Main(string[] args)  
    {  
        // Ensure using statement to automatically call Dispose  
        using (var fileHandler = new FileHandler("students.txt"))  
        {  
            string line;  
            while ((line = fileHandler.ReadLine()) != null)  
            {  
                Console.WriteLine(line);  
            }  
        }  
  
        // FileHandler will be disposed of at the end of the using block  
    }  
}

使用析构函数的最佳实践

  1. 首选 IDisposable:使用接口对资源进行确定性清理。如果未调用析构函数,则应作为释放未托管资源的回退。
  2. 避免繁重的逻辑:析构函数应包含最少的代码以释放资源。复杂的逻辑可能会导致性能问题。
  3. Suppress Finalization:如果调用了该方法,则用于阻止析构函数运行。
  4. 注意最终定稿延迟:仅依赖析构函数可能会导致资源发布延迟,因为无法保证定型的时间。

GC的作用。SuppressFinalize

当类实现终结器(析构函数)时,.NET 运行时会将实例添加到特殊的终结队列中。垃圾回收器 (GC) 最终将调用终结器来清理未管理的资源。但是,这为 GC 增加了额外的工作,除了正常的垃圾回收外,GC 还必须处理最终确定。

当您实现该方法以显式释放资源时,在该方法中调用会告诉 GC 它不需要为该对象调用终结器。这种优化很重要,原因如下:DisposeGC.SuppressFinalize(this)Dispose

  1. 性能改进:跳过终结器可以减少 GC 的工作量,因为它不再需要执行额外的步骤来完成对象。这可以提高应用程序的性能。
  2. 资源发布时间:通过调用和禁止终结,可以确保立即释放资源,而不是等待 GC 运行终结器,后者在不确定的时间发生。这提供了更可预测和及时的资源清理。Dispose
  3. 避免双重清理:如果已经清理了资源,则运行终结器是多余的,并可能导致尝试释放已释放的资源,从而可能导致错误或异常。Dispose

结论

C# 中的析构函数是清理未托管资源的重要功能,但由于其非确定性,应谨慎使用。实现接口可以更好地控制资源管理,这通常是推荐的方法。了解何时以及如何有效地使用析构函数将帮助您编写更高效、更可靠的 C# 应用程序。

阅读排行