.NET/C#基础之结构和readonly不可变结构类型

174 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第24天,点击查看活动详情

structure typestruct type 结构类型,也常常称为结构体或结构体类型。它是可以封装数据和相关功能(函数)的值类型。

值类型

结构类型

结构类型的定义和介绍

struct关键字用于定义结构类型。

比如,定义一个表示坐标的结构:

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

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

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

结构类型的变量包含该类型的实例(值类型的值语义性(value semantics)),默认,在赋值、传递参数、返回方法结果时,变量的值被复制。

由于值语义性,通常应该将结构类型定义为 不可变结构类型(immutable structure types

推荐使用结构类型设计以数据为主(数据为中心)的较小类型,通常没有行为或者行为很少(尽量不定义方法)。

.NET 中的数字(整数和实数)、布尔值、Unicode字符、time实例等都是结构类型。

当更关注于类型的行为,则推荐使用class,class类型有引用语义性(reference semantics),class类型的变量包含对实例的引用,而不是实例本身。

readonly struct 不可变结构类型

readonly修饰符(modifier)可以声明一个结构类型不可变。

只读结构体的数据成员是下列的只读形式:

  • 任何字段都要使用readonly修饰符
  • 任何属性,包括自动生成属性,必须是只读的。C# 9.0及之后的版本,属性可以有初始化访问器。

这样,只读结构体可以做到其成员不被修改,结构的状态不变。除了构造器(构造函数),所有实例成员都是只读的。

比如下面将 Coords 修改为只读,属性设置只能在初始化时。

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

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

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

readonly 实例成员

可以为不会修改结构状态的实例成员添加 readonly 修饰符声明。在无法将整个结构体类型声明为readonly时,推荐将合适的实例成员声明为readonly

readonly不能用于结构类型的实例字段,readonly成员可以调用非readonly成员(编译器会创建结构体实例的副本,并调用副本的非readonly成员,原始的结构体实例不会被修改)。

  • 修饰方法
public readonly double Sum()
{
    return X + Y;
}

还可以将readonly修饰符应用于System.Object中声明的重写(override)方法。

public readonly override string ToString() => $"({X}, {Y})";
  • 修饰属性和索引器
private int counter;
public int Counter
{
    readonly get => counter;
    set => counter = value;
}

// 有set访问器的属性不能修饰为readonly
// public readonly int MyCounter { get; set; }

public readonly double X { get; init; }

比如下面,一个结构类型的示例:

internal struct StructureType
{
    internal readonly int Id { get; init; }
    internal string Name { get; set; }
}

// 或
internal struct StructureType
{
    internal readonly int Id { get; init; }
    internal string Name { get; set; }

    public StructureType()
    {
       Id = 10;
       Name = "abc";
    }
}
// StructureType structureType = new StructureType();

StructureType structureType = new StructureType() { 
    Id= 10
};
//structureType.Id = 10; // 无法为readonly 赋值
Console.WriteLine(structureType.Id);

附:关于 C# 9 引入的初始化访问器(init accessor)

从C# 9开始,可以使用 init 关键字为属性或索引器定义一个访问器方法,它 可以使属性或索引器只能在对象构造时被赋值,这样可以强制实现不变性,在初始化后不能被改变。

如下,是在自动生成属性(auto-implemented propertie)中使用初始化访问器的示例:

class Person_InitExampleAutoProperty
{
    public int YearOfBirth { get; init; }
}

为初始化访问器的属性赋值,只能在创建对象时,其他地方赋值,编译器或vs会报错CS8852

var person = new Person_InitExampleAutoProperty()
{
    YearOfBirth = 1970
};

// person.YearOfBirth = 1970; // 错误 CS8852  只能在对象初始值设定项中或在实例构造函数或 "init" 访问器中的 "this" 或 "base" 上分配 init-only 属性或索引器 

初始化访问器在属性值保存在私有字段的声明中的使用,也就是替代了set的作用:

class Person_InitExample
{
     private int _yearOfBirth;

     public int YearOfBirth
     {
         get { return _yearOfBirth; }
         init { _yearOfBirth = value; }
     }
}

初始化访问器还可以作为表达式主体成员(expression-bodied member)使用

class Person_InitExampleExpressionBodied
{
    private int _yearOfBirth;

    public int YearOfBirth
    {
        get => _yearOfBirth;
        init => _yearOfBirth = value;
    }
}

参考