结构类型(或 struct 类型)是一种可封装数据和相关功能的值类型。使用 struct 关键字定义 struct 类型:

public struct Dian
    {
        public Dian( double x , double y )
            {
                X = x;
                Y = y;
            }

    public double X { get; private set; }
    public double Y { get; private set; }

    public override string ToString ( )
        {
            return $"({X},{Y})";
        }
    }

结构类型具有值语义。也就是说,结构类型的变量包含该类型的实例。默认情况下,在赋值时复制变量值,将参数传递给方法,并返回方法结果。对于结构类型变量,复制该类型的实例。

通常,您使用结构类型来设计提供很少或不提供行为的以数据为中心的小型类型。例如,.NET 使用结构类型来表示一个数字(包括整数和实数)、一个布尔值、一个 Unicode 字符、一个 time 实例。如果您关注类型的行为,请考虑定义一个类。class 类型具有引用语义。也就是说,class 类型的变量包含对该类型实例的引用,而不是实例本身。

由于 struct 类型具有值语义,我们建议您定义不可变结构类型。

readonly struct

可以使用 readonly 修饰符来声明 struct 类型为不可变的。readonly struct 的所有数据成员都必须是 readonly 的,如下所示:

  • 任何字段声明都必须具有 readonly 修饰符。
  • 任何属性(包括自动实现的属性)都必须是 readonly 的,或者是 init 的。仅限 init 的 setters 从 C# 版本 9 开始可用。

这样可以保证 readonly struct 的成员不会修改该 struct 的状态。这意味着除构造函数外的其他实例成员是隐式 readonly。

备注:在 readonly struct 中,可变引用类型的数据成员仍可改变其自身的状态。例如,不能替换 List < T > 实例,但可以向其中添加新元素。

下面的代码使用 init-only 属性资源库定义 readonly 结构:

public struct Dian
    {
        public Dian( double x , double y ) {
            X = x;
            Y = y;
    }
    public double X { get; init; }
    public double Y { get; init; }

    public override string ToString ( )
        {
            return $"({X},{Y})";
        }
    }

readonly 实例成员

还可以使用 readonly 修饰符来声明实例成员不会修改 struct 的状态。如果不能将整个 struct 类型声明为 readonly,可使用 readonly 修饰符标记不会修改结构状态的实例成员。

在 readonly 实例成员内,不能对 struct 的实例字段进行赋值。但是,readonly 成员可以调用非只读成员。在这种情况下,编译器创建结构实例的副本,并调用该副本上的非只读成员。因此,原始 struct 实例没有被修改。

通常,将 readonly 修饰符应用于以下类型的实例成员:

方法

public readonly double 和 ( )
    {
        return X + Y;
    }

你也可以将 readonly 修饰符应用于 override System . Object 中声明的方法的方法:

public readonly override string ToString ( ) => $"({X}, {Y})";

属性和索引器

private int counter;
public int Counter
    {
        readonly get => counter;
        set => counter = value;
    }

如果需要将 readonly 修饰符应用于属性或索引器的两个访问器,请在属性或索引器的声明中应用它。

备注:编译器将自动实现的属性的 get 访问器声明为 readonly,无论属性声明中是否存在 readonly 修饰符。

可以将 readonly 修饰符应用于具有 init 访问器的属性或索引器:
public readonly double X { get; init; }
可以将 readonly 修饰符应用于结构类型的 static 字段,但不能应用于任何其他 static 成员,例如属性或方法。

编译器可以使用 readonly 修饰符进行性能优化。

非破坏性变化

你可以使用 with 表达式来生成修改了指定属性和字段的结构类型实例的副本。使用对象初始值设定项语法来指定要修改的成员及其新值,如以下示例所示:

{
    static void Main(string[] args)
        {
            Dian d1 = new ( 0 , 0 );
            Console . WriteLine ( d1 ); // 输出:( 0,0 )

            Dian d2 = d1 with
                {
                    X = 3
                };
            Console . WriteLine ( d2 ); // 输出( 3,0 )

            Dian d3 = d1 with
                {
                    X = 1,
                    Y = 4
                };
            Console . WriteLine ( d3 ); // 输出:( 1,4 )
    }
}

public readonly struct Dian
    {
        public Dian( double x , double y )
            {
                X = x;
                Y = y;
            }

        public double X { get; init; }
        public double Y { get; init; }

        public override string ToString ( )
            {
                return $"( {X},{Y} )";
            }
    }

record struct

你可以定义 record struct 类型。record 类型提供用于封装数据的内置功能。可同时定义 record struct 和 readonly record struct 类型。record struct 不能是 ref struct。

内联数组

自 C# 12 开始,可以将内联数组声明为 struct 类型:

public struct JG字符缓冲区
    {
        public char _第一个元素 = 'a'; // 内联数组结构必须声明且只能声明一个实例字段;若对元素赋值,则必须具有显式的构造函数

        public JG字符缓冲区 ( )
            {
            }
    }

内联数组是包含相同类型的 N 个元素的连续块的结构。它是一个安全代码,等效于仅在 unsafe 代码中可用的固定缓冲区声明。内联数组是具有以下特征的 struct:

  • 它包含单个字段。
  • 结构未指定显式布局。

此外,编译器还会验证 System . Runtime . CompilerServices . InlineArrayAttribute 属性:

  • 必须大于零(> 0)。
  • 目标类型必须是结构。

在大多数情况下,可以像访问数组一样访问内联数组,以读取和写入值。此外,还可以使用范围和索引运算符。

对内联数组的单个字段的类型有最小的限制。它不能是指针类型:

[System . Runtime . CompilerServices . InlineArray ( 10 )]
public struct JG字符缓冲区
    {
        public unsafe char* _元素指针; // 警告 CS9184:作为类型参数无效,或具有作为类型参数无效的元素类型的内联数组类型不支持“内联数组”语言功能。
    }

但它可以是任何引用类型,也可以是任何值类型。

几乎可以将内联数组与任何 C# 数据结构一起使用。

内联数组是一种高级语言功能。它们适用于高性能方案,在这些方案中,内联的连续元素块比其他替代数据结构速度更快。

struct 初始化和默认值

struct 类型的变量直接包含该 struct 类型的数据。这会让未初始化的 struct(具有其默认值)和已初始化的 struct(通过构造值来存储一组值)之间存在区别。例如,考虑下面的代码:

public readonly struct JG测量
    {
        public double SJD测量值
            {
                get; init;
            }

       public string ZFC测量说明
            {
                get; init;
            }
        public JG测量 ( )
            {
                SJD测量值 = double . NaN;
                ZFC测量说明 = "未指定";
            }

        public JG测量 ( double 测量值 , string 测量说明 )
            {
                SJD测量值 = 测量值;
                ZFC测量说明 = 测量说明;
            }

        public override string ToString ( )
            {
                return $"{SJD测量值}({ZFC测量说明})";
            }
    }

static void Main(string[] args)
    {
        JG测量 CL1 = new ( );
        Console . WriteLine ( CL1 ); // 输出:NaN(未指定)

        JG测量 CL2 = default;
        Console . WriteLine ( CL2 ); // 输出:0()

        JG测量 [ ] CLs = new JG测量 [ 2 ];
        Console . WriteLine ( string . Join ( "," , CLs ) ); // 输出:0(),0()
    }

如前面的示例所示,default 表达式忽略了无参数构造函数,并生成了结构类型的默认值。结构类型数组实例化还忽略无参数构造函数并生成使用结构类型的默认值填充的数组。

你看到 default 的最常见情况是在数组中或内部存储包含变量块的其他集合中。

struct 的所有成员字段在创建时必须进行明确指定,因为 struct 类型直接存储其数据。struct 的 default 值已将所有字段明确指定为 0 或 "" 或其他默认值。调用构造函数时,必须明确指定所有字段。可以使用以下机制初始化字段:

  • 可以将字段初始化表达式添加到任何字段或自动实现的属性。
  • 可以在构造函数主体中初始化任何字段或自动属性。

从 C# 11 开始,如果你没有初始化结构中的所有字段,编译器会将代码添加到将这些字段初始化为 default 的构造函数中。分配给其 default 值的结构将初始化为 0 位模式。使用 new 初始化的 string 将初始化为 0 位模式,然后执行任何字段初始值设定项和构造函数。

public readonly struct JG测量
    {
        public double SJD测量值
            {
                get; init;
            }

        public string ZFC测量说明
            {
                get; init;
            } = "原始的测量值";

        public JG测量 ( double 测量值 , string 测量说明 )
            {
                SJD测量值 = 测量值;
                ZFC测量说明 = 测量说明;
            }

        public JG测量 ( double 测量值 )
            {
                SJD测量值 = 测量值;
            }

        public JG测量 ( string 测量说明 )
            {
                ZFC测量说明 = 测量说明;
            }

        public override string ToString ( )
            {
                return $"{SJD测量值}({ZFC测量说明})";
            }
    }

static void Main(string[] args)
    {
        JG测量 CL1 = new ( 5 );
        Console . WriteLine ( CL1 ); // 输出:5(原始的测量值)

        JG测量 CL2 = new ( );
        Console . WriteLine ( CL2 ); // 输出:0()

        JG测量 CL3 = default ( JG测量 );
        Console . WriteLine ( CL3 ); // 输出:0()
    }

每个 struct 都具有一个 public 无参数构造函数。如果要编写无参数构造函数,它必须是 public 构造函数。如果结构声明了任何字段初始值设定项,就必须显式声明一个构造函数。该构造函数不必是无参数的。如果结构声明了字段初始值设定项,但没有构造函数,编译器将报告错误。任何显式声明的构造函数(有参数或无参数)都会执行该结构的所有字段初始值设定项。没有字段初始值设定项或构造函数的赋值的所有字段均设置为 default 值。

从 C# 12 开始,struct 类型可以将主构造函数定义为其声明的一部分。主要构造函数为构造函数参数提供了简洁的语法,可在该结构的任何成员声明中的整个 struct 正文中使用。

如果结构类型的所有实例字段都是可访问的,则还可以在不使用 new 运算符的情况下对其进行实例化。在这种情况下,在首次使用实例之前必须初始化所有实例字段。下面的示例演示如何执行此操作:

public static class Dian没有new
    {
        public struct Dian
            {
                public double x , y;
            }
    }

static void Main(string[] args)
    {
        Dian没有new . Dian d;
        d . x = 1.25;
        d . y = 2.69;
        Console . WriteLine ( $"{d . x},{d . y}" ); // 输出:1.25,3.69
    }

对于内置值类型,请使用相应的文本来指定类型的值。

结构类型的设计限制

结构具有类类型的大部分功能。 也有一些例外情况:

  • 结构类型不能从其他类或结构类型继承,也不能作为类的基础类型。但是,结构类型可以实现接口。
  • 不能在结构类型中声明终结器。
  • 在 C# 11 之前,结构类型的构造函数必须初始化该类型的所有实例字段。

按引用传递结构类型变量

将结构类型变量作为参数传递给方法或从方法返回结构类型值时,将复制结构类型的整个实例。通过值传递可能会影响高性能方案中涉及大型结构类型的代码的性能。通过按引用传递结构类型变量,可以避免值复制操作。使用 ref、out、in 或 ref readonly 方法参数修饰符,指示必须按引用传递某个参数。使用 ref 返回值按引用返回方法结果。

struct 约束

你还可在 struct 中使用 struct 关键字,来指定类型参数为不可为 null 的值类型。结构类型和枚举类型都满足 struct 约束。

转换

对于任何结构类型(ref struct 类型除外),都存在与 System . ValueType 和 Syatem . Object 类型之间的 box(装箱)和 unbox(拆箱)转换。还存在结构类型和它所实现的任何接口之间的装箱和拆箱转换。


兔子码农
4 声望1 粉丝

一个酒晕子


« 上一篇
C# 的 static