C# 语言值类型之结构
结构类型(“structuretype”
或“struct 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})";
}
结构类型具有值语义。也就是说,结构类型的变量包含类型的实例。默认情况下,在分配中,通过将参数传递给方法并返回方法结果来复制变量值。对于结构类型变量,将复制该类型的实例。
通常,可以使用结构类型来设计以数据为中心的较小类型,这些类型只有很少的行为或没有行为。例如,.NET
使用结构类型来表示数字(整数和实数)、布尔值、Unicode 字符以及时间实例。如果侧重于类型的行为,请考虑定义一个类。 类类型具有引用语义。也就是说,类类型的变量包含的是对类型的实例的引用,而不是实例本身。由于结构类型具有值语义,因此建议定义不可变的结构类型。
只读结构
从 C# 7.2
开始,可以使用 readonly
修饰符来声明结构类型为不可变。readonly
结构的所有数据成员都必须是只读的,如下所示:
- 任何字段声明都必须具有
readonly
修饰符 - 任何属性(包括自动实现的属性)都必须是只读的。 在
C# 9.0
和更高版本中,属性可以具有init
访问器。
这样可以保证 readonly
结构的成员不会修改该结构的状态。在 C# 8.0
及更高版本中,这意味着除构造函数
外的其他实例成员是隐式 readonly
。
注意:在 只读
结构中,可变引用类型的数据成员仍可改变其自身的状态。例如,不能替换 List<T>
实例,但可以向其中添加新元素。
下面的代码使用 init-only
属性资源库定义 readonly
结构,此内容在 C# 9.0
及更高版本中才会生效:
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})";
}
只读实例成员
从 C# 8.0
开始,还可以使用 readonly
修饰符声明实例成员不会修改结构的状态。如果不能将整个结构类型声明为 readonly
,可使用 readonly
修饰符标记不会修改结构状态的实例成员。
在 readonly
实例成员内,不能分配到结构的实例字段。但是,readonly
成员可以调用非 readonly
成员。 在这种情况下,编译器将创建结构实例的副本,并调用该副本上的非 readonly
成员。因此,不会修改原始结构实例。
通常,将 readonly
修饰符应用于以下类型的实例成员:
方法
public readonly double Sum()
{
return X + Y;
}
还可以将
readonly
修饰符应用于可替代在System.Object
中声明的方法的方法:public readonly override string ToString() => $"({X}, {Y})";
属性和索引器:
private int counter;
public int Counter
{
readonly get => counter;
set => counter = value;
}
如果需要将 readonly
修饰符应用于属性或索引器的两个访问器,请在属性或索引器的声明中应用它。不管属性声明中是否存在 readonly
修饰符,编译器都会将自动实现的属性的 get
访问器声明为 readonly
。
在 C# 9.0
和更高版本中,可以将 readonly
修饰符应用于具有 init
访问器的属性或索引器:
public readonly double X { get; init; }
注意:不能将 readonly
修饰符应用于结构类型的静态成员。
结构类型的设计限制
设计结构类型时,具有与类类型相同的功能,但有以下例外:
不能声明无参数构造函数。 每个结构类型都已经提供了一个隐式无参数构造函数,该构造函数生成类型的默认值。
不能在声明实例字段或属性时对它们进行初始化。 但是,可以在其声明中初始化静态或常量字段或静态属性。
结构类型的构造函数必须初始化该类型的所有实例字段。
结构类型不能从其他类或结构类型继承,也不能作为类的基础类型。 但是,结构类型可以实现接口。
不能在结构类型中声明终结器。
结构类型的实例化
在 C#
中,必须先初始化已声明的变量,然后才能使用该变量。由于结构类型变量不能为 null
(除非它是可为空的值类型的变量),因此,必须实例化相应类型的实例。有多种方法可实现此目的。
通常,可使用 new
运算符调用适当的构造函数来实例化结构类型。每个结构类型都至少有一个构造函数。这是一个隐式无参数构造函数,用于生成类型的默认值。还可以使用默认值表达式来生成类型的默认值。
如果结构类型的所有实例字段都是可访问的,则还可以在不使用 new
运算符的情况下对其进行实例化。 在这种情况下,在首次使用实例之前必须初始化所有实例字段。下面的示例演示如何执行此操作:
public static class StructWithoutNew
{
public struct Coords
{
public double x;
public double y;
}
public static void Main()
{
Coords p;
p.x = 3;
p.y = 4;
Console.WriteLine($"({p.x}, {p.y})"); // output: (3, 4)
}
}
在处理内置值类型的情况下,请使用相应的文本来指定类型的值。
按引用传递结构类型变量
将结构类型变量作为参数传递给方法或从方法返回结构类型值时,将复制结构类型的整个实例。这可能会影响高性能方案中涉及大型结构类型的代码的性能。通过按引用传递结构类型变量,可以避免值复制操作。 使用ref
、 out
或 in
方法参数修饰符,指示必须按引用传递参数。 使用 ref
返回值按引用返回方法结果。
ref 结构
从 C# 7.2
开始,可以在结构类型的声明中使用 ref
修饰符。 ref
结构类型的实例在堆栈上分配,并且不能转义到托管堆。为了确保这一点,编译器将 ref
结构类型的使用限制如下:
ref 结构不能是数组的元素类型。
ref 结构不能是类或非
ref
结构的字段的声明类型。ref 结构不能实现接口。
ref 结构不能被装箱为
System.ValueType
或System.Object
。ref 结构不能是类型参数。
ref 结构变量不能由 lambda 表达式或本地函数捕获。
ref 结构变量不能在 async 方法中使用。 但是,可以在同步方法中使用
ref
结构变量,例如,在返回Task
或Task<TResult>
的方法中。ref 结构变量不能在迭代器中使用。
通常,如果需要一种同时包含 ref
结构类型的数据成员的类型,可以定义 ref
结构类型:
public ref struct CustomRef
{
public bool IsValid;
public Span<int> Inputs;
public Span<int> Outputs;
}
若要将 ref
结构声明为 readonly
,请在类型声明中组合使用 readonly
修饰符和 ref
修饰符( readonly
修饰符必须位于 ref
修饰符之前):
public readonly ref struct ConversionRequest{ public ConversionRequest(double rate, ReadOnlySpan
在 .NET
中, ref
结构的示例分别是 System.Span<T>
和 System.ReadOnlySpan<T>
。
struct 泛型约束
你还可在 struct
约束中使用 struct
关键字,来指定类型参数为不可为 null
的值类型。结构类型和枚举类型都满足 struct
约束。
public void s<T>(T t) where T : struct
{ }