17a-结构体(Struct)
结构体是值类型,适合表示轻量级数据对象。struct 不支持继承,但可以实现接口。
一、定义与基本使用
csharp
// 基本定义
public struct Point
{
public int X;
public int Y;
}
// 使用
Point p1;
p1.X = 10;
p1.Y = 20;
Console.WriteLine($"({p1.X}, {p1.Y})"); // (10, 20)
// 构造函数初始化
Point p2 = new Point(30, 40);带构造函数和属性的 struct
csharp
public struct Rectangle
{
public int Width { get; }
public int Height { get; }
public Rectangle(int width, int height)
{
Width = width;
Height = height;
}
public int Area => Width * Height;
public override string ToString() => $"{Width}×{Height}";
}
// 使用
var rect = new Rectangle(10, 20);
Console.WriteLine(rect.Area); // 200二、值类型行为
赋值复制
csharp
struct Point { public int X; public int Y; }
Point a = new Point { X = 1, Y = 2 };
Point b = a; // 复制整个数据
b.X = 999; // 修改 b 不影响 a
Console.WriteLine(a.X); // 1(a 是独立的)
Console.WriteLine(b.X); // 999作为参数传递
csharp
struct Point { public int X; public int Y; }
void ModifyPoint(Point p)
{
p.X = 100; // 修改的是副本,不影响原变量
}
var pt = new Point { X = 1, Y = 2 };
ModifyPoint(pt);
Console.WriteLine(pt.X); // 1(未被修改)
// 使用 ref 传递引用(避免复制)
void ModifyPointRef(ref Point p)
{
p.X = 100; // 修改原变量
}
ModifyPointRef(ref pt);
Console.WriteLine(pt.X); // 100集合中修改 struct 的注意事项
csharp
struct Point { public int X; public int Y; }
// ⚠️ 数组可以直接修改(因为数组元素是变量)
Point[] points = new Point[3];
points[0].X = 10; // ✅ 直接修改
// ⚠️ List 中的 struct 不能直接修改(需要取出副本再写回)
List<Point> list = new List<Point>();
list.Add(new Point { X = 1, Y = 2 });
// list[0].X = 10; // ❌ 编译错误:不能修改临时表达式
// 正确做法:取出 → 修改 → 写回
Point temp = list[0];
temp.X = 10;
list[0] = temp;三、Struct vs Class 详细对比
| 对比项 | Struct(结构体) | Class(类) |
|---|---|---|
| 类型 | 值类型 | 引用类型 |
| 存储位置 | 栈(Stack)或内嵌在父对象中 | 堆(Heap) |
| 默认值 | 所有字段置零 | null |
| 赋值行为 | 复制全部数据 | 复制引用(指向同一对象) |
| 继承 | 不支持继承,可实现接口 | 支持继承和多态 |
| 构造函数 | 必须有参数的无参构造(C# 10+ 支持显式无参构造) | 默认有无参构造,可重载 |
| 析构函数 | 不支持 | 支持 |
| null 赋值 | 不可直接赋 null(需 int? 或 Nullable<T>) | 可为 null |
| 作为参数 | 传值(方法内修改不影响原变量) | 传引用(方法内修改影响原对象) |
this | 不可在成员方法中重新赋值 this | 可在成员方法中重新赋值 this |
| 性能 | 小数据(≤16 字节)时高效 | 需要 GC 管理,额外内存开销 |
| 适用场景 | 轻量级数据对象 | 复杂业务对象 |
选择指南
| 条件 | 选 struct | 选 class |
|---|---|---|
| 数据量小(≤16 字节) | ✅ | |
| 数据量大(>16 字节) | ✅ | |
| 需要继承/多态 | ✅ | |
| 需要引用语义(共享) | ✅ | |
| 需要可变性 | ✅ | |
| 不可变的值语义 | ✅ | |
| 短生命周期(局部变量) | ✅ | |
| 需要长期存储/共享 | ✅ | |
| 频繁创建大量实例 | ✅ |
四、特殊 Struct 类型
1. readonly struct(C# 7.2+)
csharp
// 保证不可变性,编译器可做更多优化
public readonly struct ReadOnlyPoint
{
public int X { get; }
public int Y { get; }
public ReadOnlyPoint(int x, int y)
{
X = x;
Y = y;
}
// readonly struct 中所有成员都是只读的
// 不能有 setter
}
// 使用
var p = new ReadOnlyPoint(10, 20);
// p.X = 30; // ❌ 编译错误2. ref struct(C# 7.2+)
csharp
// ref struct 只能分配在栈上,不能装箱,不能作为类成员
ref struct SpanWrapper
{
public Span<int> Data { get; }
public SpanWrapper(Span<int> data)
{
Data = data;
}
}
// 限制:
// - 不能装箱(不能转为 object、interface)
// - 不能作为 class 的字段
// - 不能用于 async 方法(可能跨越 await 边界)
// - 不能用于迭代器(yield return)
// - 可用于高性能场景(Span<T>, ReadOnlySpan<T>)3. 带 readonly 成员的 struct
csharp
public struct PointWithReadonly
{
public int X { get; set; }
public int Y { get; set; }
// readonly 成员保证不修改结构体状态
public readonly double DistanceFromOrigin()
=> Math.Sqrt(X * X + Y * Y);
// readonly 属性
public readonly string Display => $"({X}, {Y})";
}五、Struct 实现接口
csharp
public interface IMovable
{
void Move(int dx, int dy);
}
public struct Point : IMovable
{
public int X;
public int Y;
public void Move(int dx, int dy)
{
X += dx;
Y += dy;
}
public override string ToString() => $"({X}, {Y})";
}
// 使用
// 直接调用(不装箱)
Point p = new Point { X = 1, Y = 2 };
p.Move(10, 20);
Console.WriteLine(p); // (11, 22)
// 通过接口调用(装箱!有性能开销)
IMovable movable = p; // 装箱
movable.Move(5, 5); // 修改的是堆上的副本注意: struct 赋值给接口变量时会发生装箱。如果性能敏感,尽量避免。
六、性能考虑
什么时候 struct 更快
csharp
// ✅ struct 更快的场景:大量小对象的短期使用
struct Point { public float X; public float Y; }
// 创建 1000 万个点的数组
Point[] points = new Point[10_000_000];
// 直接存储值,内存连续,缓存友好
// ❌ class 对比:每个对象单独分配,GC 压力大
class PointClass { public float X; public float Y; }
PointClass[] points2 = new PointClass[10_000_000];
for (int i = 0; i < points2.Length; i++)
points2[i] = new PointClass(); // 1000 万次堆分配什么时候 struct 更慢
csharp
// ❌ struct 更慢的场景:大数据结构频繁复制
struct BigData
{
public long A, B, C, D, E, F, G, H; // 64 字节
}
BigData a = new BigData();
BigData b = a; // 复制 64 字节(比引用复制 8 字节慢得多)
// ✅ 这种情况下用 class 更好
class BigDataClass { /* 同字段 */ }
BigDataClass c = new BigDataClass();
BigDataClass d = c; // 只复制 8 字节的引用七、综合案例:颜色值
csharp
// 使用 struct 表示一个 RGB 颜色
public readonly struct Color
{
public byte R { get; }
public byte G { get; }
public byte B { get; }
public byte A { get; }
public Color(byte r, byte g, byte b, byte a = 255)
{
R = r; G = g; B = b; A = a;
}
// 预定义颜色(静态属性)
public static Color Red => new Color(255, 0, 0);
public static Color Green => new Color(0, 255, 0);
public static Color Blue => new Color(0, 0, 255);
public static Color White => new Color(255, 255, 255);
public static Color Black => new Color(0, 0, 0);
// 亮度
public readonly double Luminance => 0.299 * R + 0.587 * G + 0.114 * B;
// 混合
public Color Blend(Color other, double ratio = 0.5)
{
double r = R * (1 - ratio) + other.R * ratio;
double g = G * (1 - ratio) + other.G * ratio;
double b = B * (1 - ratio) + other.B * ratio;
return new Color((byte)r, (byte)g, (byte)b, A);
}
public override string ToString() => $"#{R:X2}{G:X2}{B:X2}";
}
// 使用
var c1 = Color.Red;
var c2 = Color.Blue;
var blended = c1.Blend(c2, 0.5);
Console.WriteLine(blended); // 混合色核心知识点总结
Struct 关键特性
| 特性 | 说明 |
|---|---|
| 值类型 | 继承自 System.ValueType |
| 复制语义 | 赋值、传参时复制整个数据 |
| 无继承 | 不能作为基类或被继承,可实现接口 |
| 无析构函数 | 生命周期由作用域决定 |
| 栈分配 | 小数据时高性能,减少 GC 压力 |
| sealed 隐式 | 所有 struct 隐式 sealed |
注意事项
- struct 应小于 16 字节——过大时复制成本高,不如用 class
- 赋值即复制——修改 struct 副本不影响原变量
- 不可变性推荐——
readonly struct避免意外复制 - List 中的 struct 不能直接修改字段——需要取出 → 修改 → 写回
- 装箱发生在接口赋值时——
IMovable m = myStruct;会装箱 - 无参构造需 C# 10+——旧版本必须通过参数赋值初始化所有字段
- 适合值语义的类型——坐标、颜色、复数、货币等
- 不适合多态场景——需要继承体系时应使用 class


