C构造函数与初始化详解
立即解锁
发布时间: 2025-09-15 00:31:53 阅读量: 1 订阅数: 9 AIGC 

### C# 构造函数与初始化详解
在 C# 编程中,对象的初始化和构造函数的使用是非常重要的概念。下面将详细介绍字段初始化、构造函数的各种类型以及它们的执行顺序等内容。
#### 1. 字段初始化
非实例字段(常量和静态字段)的初始化器在结构体中总是会被执行。对于非常量字段,如果提供了初始化表达式,它不需要在编译时就可求值,因此可以进行运行时工作,如调用方法或读取属性。不过,这类代码可能会有副作用,所以了解初始化器的执行顺序很重要。
非静态字段初始化器会为每个创建的实例运行,并且按照它们在文件中出现的顺序执行,恰好在构造函数运行之前。静态字段初始化器无论创建多少该类型的实例,最多只执行一次,它们也按声明顺序执行,但确切的执行时间较难确定。
如果类没有静态构造函数,C# 保证在首次访问类中的字段之前运行字段初始化器,但不一定会等到最后一刻,它有权尽早运行。如果存在静态构造函数,静态字段初始化器会在静态构造函数运行之前立即执行。
#### 2. 构造函数
新创建的对象可能需要一些信息才能正常工作。例如,`System` 命名空间中的 `Uri` 类表示统一资源标识符(URI),如 URL。由于其目的是包含和提供关于 URI 的信息,所以不提供 URI 就无法创建 `Uri` 对象。
```csharp
// 错误示例:未提供 URI 创建 Uri 对象
Uri oops = new Uri(); // 无法编译
```
`Uri` 类定义了多个构造函数,构造函数是包含初始化类型新实例代码的成员。如果某个类需要特定信息才能工作,可以通过构造函数来强制这个要求。
定义构造函数时,首先指定可访问性(如 `public`、`private`、`internal` 等),然后是包含类型的名称,接着是括号内的参数列表(可以为空)。
```csharp
// 具有一个构造函数的类
public class Item
{
public Item(decimal price, string name)
{
_price = price;
_name = name;
}
private readonly decimal _price;
private readonly string _name;
}
```
这个构造函数很简单,只是将参数复制到字段。很多构造函数的功能也仅此而已。不过,按照惯例,开发者通常期望构造函数做的事情尽量少,其主要工作是确保对象处于有效的初始状态。
使用构造函数时,只需使用 `new` 运算符,并传入合适类型的值作为参数。
```csharp
// 使用构造函数
var item1 = new Item(9.99M, "Hammer");
```
可以定义多个构造函数,但必须能够区分它们,不能定义两个接受相同数量和类型参数的构造函数,因为 `new` 关键字无法知道你想要调用哪一个。
记录类型在定义时实际上也定义了构造函数。
```csharp
// 具有编译器生成构造函数的记录类型
public record Item(decimal Price, string Name);
```
编译器会生成一个接受 `decimal` 和 `string` 参数的构造函数,用于初始化 `Price` 和 `Name` 属性。如果编译器生成的构造函数能满足需求,使用起来会很方便。
#### 3. 默认构造函数和无参数构造函数
如果根本不定义任何构造函数,C# 会提供一个默认构造函数,相当于一个空的无参数构造函数。对于结构体,即使定义了其他构造函数,也会有默认构造函数。
需要注意的是,“默认构造函数” 有两种含义。C# 规范明确将其定义为由编译器生成的构造函数,但在实际中,它也常用来表示任何公共的无参数构造函数,无论是否由编译器生成。
编译器生成的默认构造函数除了对字段进行零初始化外,不做其他事情。但在某些情况下,需要编写自己的无参数构造函数。例如,可能需要构造函数执行一些代码。
```csharp
// 非空的无参数构造函数
public class ItemWithId
{
private static int _lastId;
private int _id;
public ItemWithId()
{
_id = ++_lastId;
}
}
```
也可以通过编写静态方法来实现相同的效果,但将代码放在构造函数中有一个优势:字段初始化器不允许调用对象自身的非静态方法,但构造函数可以。这是因为在字段初始化期间,对象处于不完整状态,调用非静态方法可能有危险。
如果为类定义了至少一个构造函数,默认构造函数的生成将被禁用。如果需要类提供参数化构造,同时又想提供无参数构造函数,就需要自己编写,即使它是空的。另外,如果想编写一个只有空的无参数构造函数,但保护级别不是默认的公共级别(如设为私有),也需要显式编写构造函数。
对于结构体,无参数构造函数的工作方式略有不同。由于值类型需要支持隐式初始化,当值类型用作其他类型的字段或数组的元素类型时,CLR 会将其内存初始化为零。因此,即使 C# 在类中添加带参数的构造函数时会移除默认构造函数,但对于结构体不会这样做。在 C# 10.0 之前,不允许为结构体编写无参数构造函数,现在可以编写,但要注意它只在代码显式调用构造函数时才会运行,大多数情况下使用的是 CLR 的零初始化。
还有一种编译器生成的构造函数:当编写记录或记录类时,编译器会生成一个构造函数,用于在使用 `with` 语法创建副本时使用。默认情况下,它执行浅拷贝,也可以自己编写实现。
```csharp
// 具有自定义复制构造函数的记录类型
public record ValueWithId(int Value, int Id)
{
public ValueWithId(ValueWithId source)
{
Value = source.Value;
Id = source.Id + 1;
}
}
```
编译器不会为记录结构体生成复制构造函数,因为所有结构体类型本质上都是可复制的。
#### 4. 构造函数链
如果一个类型定义了多个构造函数,它们可能有一些共同的初始化任务。可以通过构造函数链来解决这个问题。
```csharp
// 可选的构造函数链
public class ItemWithId
{
private static int _lastId;
private int _id;
private stri
```
0
0
复制全文
相关推荐









