小窍门
开发软件的新手? 首先开始 学习入门 教程。 在代码中需要轻型值类型后,你将遇到结构。
是否在其他语言中有经验? C# 结构体是类似于 C++ 或 Swift 中的结构体的值类型,但在进行装箱时,它们将驻留于托管堆上,并支持接口、构造函数和方法。 浏览 只读结构 部分以查找 C# 特定模式。 有关记录结构,请参阅 记录。
结构是一种值类型,它直接在实例中保存其数据,而不是通过对堆上的对象的引用。 将结构分配给新变量时,运行时将复制整个实例。 对一个变量的更改不会影响另一个变量,因为每个变量都表示不同的实例。 选择使用结构体来处理小型轻量的类型,这些类型的主要作用是存储数据而不是行为建模。 示例包括坐标、颜色、度量或配置设置。
何时使用结构
在类型时使用结构:
- 表示单个值或一小组相关值(大约 16 个字节或更少)。
- 具有值语义 — 两个具有相同数据的实例应相等。
- 主要是数据容器,而不是行为模型。
- 不需要从基类型继承(结构不能从其他结构或类继承,但它们可以实现接口)。
有关包括类、记录、元组和接口的更广泛比较,请参阅 “选择哪种类型”。
声明结构体
使用 struct 关键字定义结构。 结构可以包含字段、属性、方法和构造函数,就像类一样:
struct Point
{
public double X { get; set; }
public double Y { get; set; }
public readonly double DistanceTo(Point other)
{
var dx = X - other.X;
var dy = Y - other.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
public override string ToString() => $"({X}, {Y})";
}
结构 Point 存储两 double 个值,并提供一种方法来计算两个点之间的距离。 方法 DistanceTo 被标记 readonly ,因为它不会修改结构的状态。 该模式包含在 只读成员中。
值语义
结构是 值类型。 赋值复制数据,因此每个变量都有自己的独立副本:
var p1 = new Point { X = 3, Y = 4 };
var p2 = p1; // copies the data
p2.X = 10;
Console.WriteLine(p1); // (3, 4) — p1 is unchanged
Console.WriteLine(p2); // (10, 4) — only p2 was modified
由于结构是数据容器,因此分配会将每个数据成员复制到新的独立实例中。 每个副本都是不同的。 修改一个不会影响另一个。 此行为不同于 类,其中赋值仅复制引用,并且两个变量共享同一对象。 有关区别的详细信息,请参阅 “值类型”和“引用类型”。
结构构造函数
可以采用与类中相同的方式在结构中定义构造函数。 结构可以具有设置自定义默认值的 无参数构造函数 。 术语“无参构造函数”用于区分使用new(执行构造函数逻辑)创建的实例与使用表达式创建的default实例(从零初始化所有字段):
struct ConnectionSettings
{
public string Host { get; set; }
public int Port { get; set; }
public int MaxRetries { get; set; }
public ConnectionSettings()
{
Host = "localhost";
Port = 8080;
MaxRetries = 3;
}
}
无参数构造函数在 new 不使用任何参数时运行。 表达式default绕过构造函数并将所有字段设置为其默认值 (0, , nullfalse)。 请注意差异:
var custom = new ConnectionSettings();
Console.WriteLine($"{custom.Host}:{custom.Port} (retries: {custom.MaxRetries})");
// localhost:8080 (retries: 3)
var defaults = default(ConnectionSettings);
Console.WriteLine($"{defaults.Host ?? "(null)"}:{defaults.Port} (retries: {defaults.MaxRetries})");
// (null):0 (retries: 0)
编译器会自动初始化未在构造函数中显式设置的任何字段。 只能初始化需要非默认值的字段:
struct GameTile
{
public int Row { get; set; }
public int Column { get; set; }
public bool IsBlocked { get; set; }
public GameTile(int row, int column)
{
Row = row;
Column = column;
// IsBlocked is automatically initialized to false
}
}
以下示例显示默认值 IsBlocked:
var tile = new GameTile(2, 5);
Console.WriteLine($"Tile ({tile.Row}, {tile.Column}), blocked: {tile.IsBlocked}");
// Tile (2, 5), blocked: False
该IsBlocked属性未在构造函数中赋值,因此编译器将其设置为false,即bool的默认值。 此功能减少了构造函数中只需设置几个字段时的冗余代码。
Readonly 结构和只读成员
readonly struct 保证实例成员不会修改结构体的状态。 编译器要求所有字段和自动实现的属性都是只读的,从而强制实施此保证:
readonly struct Temperature
{
public double Celsius { get; }
public Temperature(double celsius) => Celsius = celsius;
public double Fahrenheit => Celsius * 9.0 / 5.0 + 32.0;
public override string ToString() => $"{Celsius:F1}°C ({Fahrenheit:F1}°F)";
}
以下示例创建一个 Temperature 实例并读取其属性:
var temp = new Temperature(100);
Console.WriteLine(temp); // 100.0°C (212.0°F)
// temp.Celsius = 50; // Error: property is read-only
如果不需要整个结构是不可变的,请改为将各个成员 readonly 标记为不可变。 成员 readonly 无法修改结构的状态,编译器会验证该保证:
struct Velocity
{
public double X
{
readonly get;
set;
}
public double Y
{
readonly get;
set;
}
public readonly double Speed => Math.Sqrt(X * X + Y * Y);
public readonly override string ToString() => $"({X}, {Y}) speed={Speed:F2}";
}
以下示例显示 readonly 成员在可变属性更改时返回更新的值:
var v = new Velocity { X = 3, Y = 4 };
Console.WriteLine(v.Speed); // 5
Console.WriteLine(v); // (3, 4) speed=5.00
v.X = 6;
Console.WriteLine(v.Speed); // 7.211...
标记成员 readonly 有助于编译器优化防御性副本。 将结构传递给 readonly 接受 in 参数的方法时,编译器知道不需要复制。