Skip to content

C# - 结构体

在 C# 中,结构体(struct)是一种值类型,用于封装少量相关变量。它们非常适合表示以数据为中心、轻量级的对象,且不需要像继承那样的复杂行为。

可以将结构体视为创建单个变量的蓝图,该变量包含相关的数据点。例如,要表示 RGB 颜色,你可能需要红色(Red)、绿色(Green)和蓝色(Blue)值。结构体可以将这些值组合在一起:

public readonly struct RgbColor
{
public byte Red { get; }
public byte Green { get; }
public byte Blue { get; }
public RgbColor(byte r, byte g, byte b)
{
Red = r;
Green = g;
Blue = b;
}
}

关键区别: 最重要的区别在于结构体是值类型,而类是引用类型。当你将结构体传递给方法时,会传递结构体的副本。当你传递类实例时,会传递对该实例的引用。

你可以使用 struct 关键字定义结构体。现代 C# 的最佳实践鼓励使用 readonly 关键字以及 init 或仅 get 属性使结构体不可变。这可以防止它们在创建后被更改状态,从而带来更可预测的代码。

使用主构造函数定义现代结构体(C# 12+)

Section titled “使用主构造函数定义现代结构体(C# 12+)”

C# 12 引入了主构造函数,以实现更简洁的语法。

// 推荐的现代方法(C# 12+)
public readonly struct Point(double x, double y)
{
public double X { get; } = x;
public double Y { get; } = y;
// 结构体中的方法
public double DistanceFromOrigin() => Math.Sqrt(X * X + Y * Y);
// 覆写 ToString 方法以更好地显示
public override string ToString() => $"({X}, {Y})";
}

你可以使用 new 关键字初始化结构体,这会调用其构造函数。

// 使用上面 Point 结构体的主构造函数
var p1 = new Point(3.0, 4.0);
var p2 = new Point(-1.0, 5.5);
Console.WriteLine($"Point 1: {p1}");
Console.WriteLine($"Point 1 Distance: {p1.DistanceFromOrigin():F2}"); // F2 格式化为两位小数
// 输出:
// Point 1: (3, 4)
// Point 1 Distance: 5.00

注意: 与类不同,你可以在不使用 new 的情况下实例化结构体,但在使用它之前必须初始化其所有字段/属性。使用 new 通常更安全,因为它确保所有成员都初始化为它们的默认值。

在类和结构体之间做出选择是一个关键的设计决策。当类型满足以下大部分条件时,请使用结构体:

  • 它逻辑上表示一个单一的值,例如数字、日期或点。
  • 它占用内存较小(通常小于 16 字节)。
  • 它是不可变的。一旦创建,其值不应改变。
  • 它不需要频繁地装箱或拆箱(装箱是将值类型转换为引用类型,这可能会影响性能)。

对于其他所有情况,请使用类,特别是对于需要继承、具有唯一标识或其状态会随时间变化的更大、更复杂的对象。

让我们创建一个小程序来管理图书记录。我们将定义一个 Book 结构体并在列表中使用它。

using System;
using System.Collections.Generic;
// 定义一个不可变的 Book 结构体
public readonly struct Book
{
public string Title { get; init; }
public string Author { get; init; }
public int PublicationYear { get; init; }
// 覆写 ToString 方法以获得整洁、可读的输出
public override string ToString() =>
$"'{Title}' by {Author} ({PublicationYear})";
}
// 主应用程序逻辑(使用顶级语句)
var library = new List<Book>
{
new() { Title = "1984", Author = "George Orwell", PublicationYear = 1949 },
new() { Title = "Dune", Author = "Frank Herbert", PublicationYear = 1965 },
new() { Title = "The Hitchhiker's Guide to the Galaxy", Author = "Douglas Adams", PublicationYear = 1979 }
};
Console.WriteLine("Welcome to the Library!");
Console.WriteLine("-----------------------");
foreach (var book in library)
{
Console.WriteLine(book);
}
// 如何测试这个逻辑?
// 你可以编写一个单元测试来验证创建 Book 并调用 ToString()
// 能产生预期的字符串格式。

当上述代码编译并执行时,会产生以下结果:

Welcome to the Library!
-----------------------
'1984' by George Orwell (1949)
'Dune' by Frank Herbert (1965)
'The Hitchhiker's Guide to the Galaxy' by Douglas Adams (1979)

结构体与类一样,可以实现接口。这对于在不同类型之间提供通用功能很有用。

using System;
// 可表示为字符串的对象的接口
public interface IFormattable
{
string ToFormattedString();
}
// 实现接口的结构体
public readonly struct Person(string firstName, string lastName) : IFormattable
{
public string FirstName { get; } = firstName;
public string LastName { get; } = lastName;
// 使用表达式主体实现接口方法
public string ToFormattedString() => $"Name: {LastName}, {FirstName}";
}
// --- 主程序 ---
var person = new Person("John", "Doe");
Console.WriteLine(person.ToFormattedString());
// 输出:
// Name: Doe, John