in
修饰符也是从 C# 7.2 开始引入的,它与我们上一篇中讨论的 《C# 中的只读结构体(readonly struct)》1 是紧密相关的。
in 修饰符
in
修饰符通过引用传递参数。 它让形参成为实参的别名,即对形参执行的任何操作都是对实参执行的。 它类似于 ref
或 out
关键字,不同之处在于 in
参数无法通过调用的方法进行修改。
ref
修饰符,指定参数由引用传递,可以由调用方法读取或写入。out
修饰符,指定参数由引用传递,必须由调用方法写入。in
修饰符,指定参数由引用传递,可以由调用方法读取,但不可以写入。
举个简单的例子:
struct Product
{
public int ProductId {
get; set; }
public string ProductName {
get; set; }
}
public static void Modify(in Product product)
{
//product = new Product(); // 错误 CS8331 无法分配到 变量 'in Product',因为它是只读变量
//product.ProductName = "测试商品"; // 错误 CS8332 不能分配到 变量 'in Product' 的成员,因为它是只读变量
Console.WriteLine($"Id: {product.ProductId}, Name: {product.ProductName}"); // OK
}
引入 in 参数的原因
我们知道,结构体实例的内存在栈(stack)上进行分配,所占用的内存随声明它的类型或方法一起回收,所以通常在内存分配上它是比引用类型占有优势的。2
但是对于有些很大(比如有很多字段或属性)的结构体,将其作为方法参数,在紧凑的循环或关键代码路径中调用方法时,复制这些结构的成本就会很高。当所调用的方法不修改该参数的状态,使用新的修饰符 in
声明参数以指定此参数可以按引用安全传递,可以避免(可能产生的)高昂的复制成本,从而提高代码运行的性能。
in 参数对性能的提升
为了测试 in
修饰符对性能的提升,我定义了两个较大的结构体,一个是可变的结构体 NormalStruct
,一个是只读的结构体 ReadOnlyStruct
,都定义了 30 个属性,然后定义三个测试方法:
DoNormalLoop
方法,参数不加修饰符,传入一般结构体,这是以前比较常见的做法。DoNormalLoopByIn
方法,参数加in
修饰符,传入一般结构体。DoReadOnlyLoopByIn
方法,参数加in
修饰符,传入只读结构体。
代码如下所示:
public struct NormalStruct
{
public decimal Number1 {
get; set; }
public decimal Number2 {
get; set; }
//...
public decimal Number30 {
get; set; }
}
public readonly struct ReadOnlyStruct
{
// 自动属性上的 readonly 关键字是可以省略的,这里加上是为了便于理解
public readonly decimal Number1 {
get; }
public readonly decimal Number2 {
get; }
//...
public readonly decimal Number30 {
get; }
}
public class BenchmarkClass
{
const int loops = 50000000;
NormalStruct normalInstance =