前言
i来&源gao@dai!ma.com搞$代^码%网n 修饰符也是从 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 { public readonly decimal Number1 { get; } public readonly decimal Number2 { get; } //... public readonly decimal Number30 { get; } } public class BenchmarkClass { const int loops = 50000000; NormalStruct normalInstance = new NormalStruct(); ReadOnlyStruct readOnlyInstance = new ReadOnlyStruct(); [Benchmark(Baseline = true)] public decimal DoNormalLoop() { decimal result = 0M; for (int i = 0; i < loops; i++) { result = Compute(normalInstance); } return result; } [Benchmark] public decimal DoNormalLoopByIn() { decimal result = 0M; for (int i = 0; i < loops; i++) { result = ComputeIn(in normalInstance); } return result; } [Benchmark] public decimal DoReadOnlyLoopByIn() { decimal result = 0M; for (int i = 0; i < loops; i++) { result = ComputeIn(in readOnlyInstance); } return result; } public decimal Compute(NormalStruct s) { //业务逻辑 return 0M; } public decimal ComputeIn(in NormalStruct s) { //业务逻辑 return 0M; } public decimal ComputeIn(in ReadOnlyStruct s) { //业务逻辑 return 0M; } }