在C#的面向对象编程体系中,构造函数和析构函数是和类生命周期紧密相关的两个特殊成员,它们不需要开发者手动调用,会在对象创建和销毁的特定阶段自动执行,是管理对象状态和资源的常用工具。

C#构造函数基础概念
构造函数是类的一个特殊方法,它的名称必须和类名完全一致,并且没有返回值,连void关键字都不能写。当我们使用new关键字创建类的实例时,构造函数会自动被调用,主要作用是给对象的字段、属性赋初始值,完成对象的初始化工作。
如果我们在定义类的时候没有手动编写构造函数,C#编译器会自动生成一个无参数的默认构造函数,这个默认构造函数内部没有任何执行逻辑,只是保证对象可以被正常创建。一旦我们手动编写了任意构造函数,编译器就不会再生成默认构造函数了。
构造函数的基本示例
下面是一个最简单的构造函数使用示例:
using System;
namespace ConstructorDemo
{
public class Person
{
// 定义两个字段
public string Name;
public int Age;
// 手动编写的无参数构造函数
public Person()
{
Name = "默认姓名";
Age = 0;
Console.WriteLine("无参数构造函数执行了");
}
}
class Program
{
static void Main(string[] args)
{
// 创建Person实例,会自动调用无参数构造函数
Person p = new Person();
Console.WriteLine($"姓名:{p.Name},年龄:{p.Age}");
}
}
}
构造函数的分类与重载
构造函数可以根据参数情况分为无参数构造函数和有参数构造函数,同一个类中可以定义多个参数不同的构造函数,这就是构造函数的重载,方便我们在创建对象时传入不同的初始数据。
有参数构造函数示例
我们可以在上面的Person类中添加有参数的构造函数:
public class Person
{
public string Name;
public int Age;
// 无参数构造函数
public Person()
{
Name = "默认姓名";
Age = 0;
}
// 有参数构造函数,接收姓名和年龄作为参数
public Person(string name, int age)
{
Name = name;
Age = age;
Console.WriteLine("有参数构造函数执行了");
}
}
创建对象时就可以根据需要选择不同的构造函数:
// 调用无参数构造函数
Person p1 = new Person();
// 调用有参数构造函数
Person p2 = new Person("张三", 20);
Console.WriteLine($"p2姓名:{p2.Name},年龄:{p2.Age}");
构造函数间的调用
如果一个类有多个构造函数,并且部分初始化逻辑是重复的,我们可以使用this关键字让一个构造函数调用另一个构造函数,减少代码冗余。语法是在构造函数后面加: this(参数)。
public class Person
{
public string Name;
public int Age;
public string Gender;
// 最完整的构造函数,包含所有字段的初始化
public Person(string name, int age, string gender)
{
Name = name;
Age = age;
Gender = gender;
}
// 调用上面的三个参数构造函数,性别默认设为未知
public Person(string name, int age) : this(name, age, "未知")
{
}
// 调用两个参数的构造函数,年龄默认设为0
public Person(string name) : this(name, 0)
{
}
}
静态构造函数
除了实例构造函数,C#还支持静态构造函数,它的作用是初始化类的静态成员,静态构造函数的特点如下:
- 名称同样是类名,没有参数,没有访问修饰符
- 在一个应用程序域中,静态构造函数只会执行一次,在第一次访问类的静态成员或者创建类的第一个实例之前执行
- 不能手动调用,由CLR自动调用
静态构造函数示例:
public class Config
{
// 静态字段
public static string AppName;
// 静态构造函数
static Config()
{
AppName = "我的应用程序";
Console.WriteLine("静态构造函数执行了");
}
public Config()
{
Console.WriteLine("实例构造函数执行了");
}
}
当我们第一次访问Config.AppName或者第一次创建Config实例时,静态构造函数就会执行。
C#析构函数详解
析构函数也叫终结器,用于在对象被垃圾回收器回收之前执行一些清理工作,比如释放非托管资源(如文件句柄、数据库连接、网络套接字等)。析构函数的特点如下:
- 名称是在类名前面加
~符号,没有参数,没有返回值,也没有访问修饰符 - 一个类只能有一个析构函数
- 不能继承也不能重载
- 执行时机不确定,由垃圾回收器决定什么时候调用,开发者无法手动控制
析构函数的基本示例:
public class FileHandler
{
private IntPtr fileHandle; // 模拟非托管文件句柄
public FileHandler()
{
// 模拟打开文件获取句柄
fileHandle = new IntPtr(123);
Console.WriteLine("文件打开,句柄:" + fileHandle);
}
// 析构函数,释放非托管资源
~FileHandler()
{
// 模拟释放文件句柄的逻辑
Console.WriteLine("析构函数执行,释放文件句柄:" + fileHandle);
}
}
需要注意的是,因为析构函数的执行时机不确定,如果我们需要确定性地释放资源,更推荐使用IDisposable接口配合using语句,而不是依赖析构函数。析构函数通常作为资源释放的最后一道保障,防止开发者忘记手动释放资源。
构造函数和析构函数的对比
我们可以通过下面的表格清晰看到两者的区别:
| 对比项 | 构造函数 | 析构函数 |
|---|---|---|
| 调用时机 | 创建对象实例时自动调用 | 对象被垃圾回收前自动调用 |
| 名称规则 | 和类名完全相同 | 类名前加~符号 |
| 参数和返回值 | 可以有参数,无返回值 | 无参数,无返回值 |
| 访问修饰符 | 可以有(通常是public) | 不能有 |
| 调用方式 | new关键字触发,可重载,可互相调用 | GC触发,不能重载,不能手动调用 |
| 主要作用 | 初始化对象实例的成员 | 释放对象占用的资源 |
使用注意事项
在实际开发中使用构造函数和析构函数时,需要注意以下几点:
- 不要在构造函数中做太多耗时的操作,否则会拖慢对象创建的速度
- 如果类需要释放非托管资源,优先实现
IDisposable接口,析构函数作为兜底方案 - 静态构造函数不要抛出异常,否则这个类型在整个应用程序域中都无法正常使用
- 不要在析构函数中访问其他可能被回收的对象,避免引发异常
构造函数和析构函数是C#类生命周期管理的重要工具,理解它们的工作机制可以帮助我们写出更规范的面向对象代码,正确处理对象的初始化和资源释放问题。