← 全部文章 C# 语言

C# 结构和类的区别

2024 年 6 月 16 日 · 2 分钟读完

目录
  1. 结构体(struct)
  2. 类(class)
  3. 对比
  4. 延伸

项目里正好同时碰到结构体和类,把两者的差异和选用场景梳理一下。

结构体(struct)

结构体是值类型,可以包含字段、属性、方法、事件、运算符、索引器等成员。

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public void Display() => Console.WriteLine($"X: {X}, Y: {Y}");
}

特点:

  • 值语义:赋值给另一个变量时复制的是整份数据,改其中一个不影响另一个。
  • 不能继承:不能继承其他结构体或类(隐式继承自 System.ValueType),但可以实现接口。
  • 小而高效:作为局部变量时通常分配在栈上,不需要 GC 回收,小型数据用结构体开销更小。
  • 默认值:default(Point) 会把所有字段置为 0 / null。C# 10 之前不能自己写无参构造函数,C# 10 起可以。
  • 必须完全初始化:自定义构造函数里要给所有字段赋值(C# 11 起编译器会自动把没赋值的字段补成默认值)。

类(class)

类是引用类型,是面向对象编程的基础,通过继承和多态实现复用和扩展。

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public void Display() => Console.WriteLine($"Name: {Name}, Age: {Age}");
}

特点:

  • 引用语义:实例分配在托管堆上,变量里存的是引用。两个变量指向同一个对象时,改一个另一个也变。
  • 继承和多态:单继承、多接口,基类变量可以指向子类对象。
  • 构造函数:可以有多个;不写时编译器提供一个默认的无参构造函数。
  • 由 GC 回收:没有引用以后,等垃圾回收器回收,见 什么对象会被 .NET 垃圾回收。

对比

struct class
类型 值类型 引用类型
赋值、传参 复制整份数据 复制引用
继承 只能实现接口 支持继承和多态
默认值 各字段为默认值 null
回收 随所在位置释放 由 GC 回收
适合 小、简单、频繁创建的数据 复杂、需要共享状态或继承的对象

选类:需要多个地方共享同一个实例;需要继承和多态;对象较大、生命周期较长。

选结构体:需要副本之间互不影响;数据小而简单(微软的建议是 16 字节以内);频繁创建和销毁,比如坐标点、颜色、一次 PLC 读数。

延伸

  • 「值类型在栈上」不完全对:准确的说法是值类型存在它所在的地方。局部变量里的 struct 在栈上;但作为类的字段、数组元素,或者被装箱成 object 时,它在堆上。
  • 装箱的代价:把 struct 赋给 object 或接口变量会装箱,在堆上复制一份。ArrayList、非泛型集合、string.Format("{0}", point) 都会装箱,高频场景用泛型集合避免它。
  • 可变结构体的坑:list[0].X = 5; 编译不过,因为 list[0] 返回的是副本;foreach 里改 struct 也改不到原数据。所以结构体最好做成不可变的:C# 7.2 的 readonly struct,或者 C# 10 的 record struct。
  • 传大结构体用 in:结构体较大又不想复制时,参数写成 in Point p,按只读引用传递。
  • Halcon 和 OpenCV 里的例子:OpenCvSharp.Point、Rect、Scalar 都是 struct,频繁创建也没有 GC 压力;而 Mat、HObject 是 class,因为它们持有大块的非托管图像内存,需要 Dispose。

# C# # 值类型 # 引用类型

原文首发于 blog.csdn.net