Skip to content

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

注意事项

  1. struct 应小于 16 字节——过大时复制成本高,不如用 class
  2. 赋值即复制——修改 struct 副本不影响原变量
  3. 不可变性推荐——readonly struct 避免意外复制
  4. List 中的 struct 不能直接修改字段——需要取出 → 修改 → 写回
  5. 装箱发生在接口赋值时——IMovable m = myStruct; 会装箱
  6. 无参构造需 C# 10+——旧版本必须通过参数赋值初始化所有字段
  7. 适合值语义的类型——坐标、颜色、复数、货币等
  8. 不适合多态场景——需要继承体系时应使用 class

Released under the MIT License.