.NET 零开销抽象指南( 二 )

ref,则一定不会是引用 。
当然,配套而来的便是返回只读引用,确保返回的引用是不可修改的 。与 ref 一样,ref readonly 也是可以作为变量来使用的:
ref readonly int Foo(int[] array){return ref array[3];}int[] array = new[] { 1, 2, 3, 4, 5 };ref readonly int x = ref Foo(array);x = 5; // 错误ref readonly int y = ref array[1];y = 3; // 错误ref structC# 7.2 引入了一种新的类型:ref struct 。这种类型由编译器和运行时同时确保绝对不会被装箱,因此这种类型的实例的生命周期非常明确,它只可能在栈内存中,而不可能出现在堆内存中:
Foo[] foos = new Foo[] { new(), new() }; // 错误ref struct Foo{public int X;public int Y;}借助 ref struct,我们便能在 ref struct 中保存引用,而无需担心 ref struct 的实例因为生命周期被意外延长而导致出现无效引用 。
Span<T>ReadOnlySpan<T>从 .NET Core 2.1 开始,.NET 引入了 Span<T>ReadOnlySpan<T> 这两个类型来表示对一段连续内存的引用和只读引用 。
Span<T>ReadOnlySpan<T> 都是 ref struct,因此他们绝对不可能被装箱,这确保了只要在他们自身的生命周期内,他们所引用的内存绝对都是有效的,因此借助这两个类型,我们可以代替指针来安全地操作任何连续内存 。
Span<int> x = new[] { 1, 2, 3, 4, 5 };x[2] = 0;void* ptr = NativeMemory.Alloc(1024);Span<int> y = new Span<int>(ptr, 1024 / sizeof(int));y[4] = 42;NativeMemory.Free(ptr);我们还可以在 foreach 中使用 refref readonly 来以引用的方式访问各成员:
Span<int> x = new[] { 1, 2, 3, 4, 5 };foreach (ref int i in x) i++;foreach (int i in x) Console.WriteLine(i); // 2 3 4 5 6stackalloc在 C# 中,除了 new 之外,我们还有一个关键字 stackalloc,允许我们在栈内存上分配数组:
Span<int> array = stackalloc[] { 1, 2, 3, 4, 5 };这样我们就成功在栈上分配出了一个数组,这个数组的生命周期就是所在代码块的生命周期 。
ref field我们已经能够在局部变量中使用 refref readonly 了,自然,我们就想要在字段中也使用这些东西 。因此我们在 C# 11 中迎来了 refref readonly 字段 。
字段的生命周期与包含该字段的类型的实例相同,因此,为了确保安全,refref readonly 必须在 ref struct 中定义,这样才能确保这些字段引用的东西一定是有效的:
int x = 1;Foo foo = new Foo(ref x);foo.X = 2;Console.WriteLine(x); // 2Bar bar = new Bar { X = ref foo.X };x = 3;Console.WriteLine(bar.X); // 3bar.X = 4; // 错误ref struct Foo{public ref int X;public Foo(ref int x){X = ref x;}}ref struct Bar{public ref readonly int X;}当然,上面的 Bar 里我们展示了对只读内容的引用,但是字段本身也可以是只读的,于是我们就还有:
ref struct Bar{public ref int X; // 引用可变内容的可变字段public ref readonly int Y; // 引用只读内容的可变字段public readonly ref int Z; // 引用可变内容的只读字段public readonly ref readonly int W; // 引用只读内容的只读字段}scopedUnscopedRef我们再看看上面这个例子的 Foo,这个 ref struct 中有接收引用作为参数的构造函数,这次我们不再在字段中保存引用:
Foo Test(){Span<int> x = stackalloc[] { 1, 2, 3, 4, 5 };Foo foo = new Foo(ref x[0]); // 错误return foo;}ref struct Foo{public Foo(ref int x){x++;}}

经验总结扩展阅读