你会发现这时代码无法编译了 。
因为 stackalloc
出来的东西仅在 Test
函数的生命周期内有效,但是我们有可能在 Foo
的构造函数中将 ref int x
这一引用存储到 Foo
的字段中,然后由于 Test
方法返回了 foo
,这使得 foo
的生命周期被扩展到了调用 Test
函数的函数上,有可能导致本身应该在 Test
结束时就释放的 x[0]
的生命周期被延长,从而出现无效引用 。因此编译器拒绝编译了 。
你可能会好奇,编译器在理论上明明可以检测到底有没有实际的代码在字段中保存了引用,为什么还是直接报错了?这是因为,如果需要检测则需要实现复杂度极其高的过程分析,不仅会大幅拖慢编译速度,而且还存在很多无法静态处理的边缘情况 。
那要怎么处理呢?这个时候 scoped
就出场了:
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(scoped ref int x){x++;}}
我们只需要在 ref
前加一个 scoped
,显式标注出 ref int x
的生命周期不会超出该函数,这样我们就能通过编译了 。
此时,如果我们试图在字段中保存这个引用的话,编译器则会有效的指出错误:
ref struct Foo{public ref int X;public Foo(scoped ref int x){X = ref x; // 错误}}
同样的,我们还可以在局部变量中配合 ref
或者 ref readonly
使用 scoped
:
Span<int> a = stackalloc[] { 1, 2, 3, 4, 5 };scoped ref int x = ref a[0];scoped ref readonly int y = ref a[1];foreach (scoped ref int i in a) i++;foreach (scoped ref readonly int i in a) Console.WriteLine(i); // 2 3 4 5 6x++;Console.WriteLine(a[0]); // 3a[1]++;Console.WriteLine(y); // 4
当然,上面这个例子中即使不加 scoped
,也是默认 scoped
的,这里标出来只是为了演示,实际上与下面的代码等价:
Span<int> a = stackalloc[] { 1, 2, 3, 4, 5 };ref int x = ref a[0];ref readonly int y = ref a[1];foreach (ref int i in a) i++;foreach (ref readonly int i in a) Console.WriteLine(i); // 2 3 4 5 6x++;Console.WriteLine(a[0]); // 3a[1]++;Console.WriteLine(y); // 4
对于 ref struct
而言,由于其自身就是一种可以保存引用的“类引用”类型,因此我们的 scoped
也可以用于 ref struct
,表明该 ref struct
的生命周期就是当前函数:
Span<int> Foo(Span<int> s){return s;}Span<int> Bar(scoped Span<int> s){return s; // 错误}
有时候我们希望在 struct
中返回 this
上成员的引用,但是由于 struct
的 this
有着默认的 scoped
生命周期,因此此时无法通过编译 。这个时候我们可以借助 [UnscopedRef]
来将 this
的生命周期从当前函数延长到调用函数上:
Foo foo = new Foo();foo.RefX = 42;Console.WriteLine(foo.X); // 42struct Foo{public int X;[UnscopedRef]public ref int RefX => ref X;}
这对 out
也是同理的,因为 out
也是默认有 scoped
生命周期:
ref int Foo(out int i){i = 42;return ref i; // 错误}
但是我们同样可以添加 [UnscopedRef]
来扩展生命周期:
ref int Foo([UnscopedRef] out int i){i = 42;return ref i; // 错误}
Unsafe
、Marshal
、MemoryMarshal
、CollectionsMarshal
、NativeMemory
和 Buffer
在 .NET 中,我们有着非常多的工具函数,分布在
经验总结扩展阅读
- 二、.Net Core搭建Ocelot
- 一千零一夜柏海的结局是什么?
- 创建.NET程序Dump的几种姿势
- 中国6大奇葩零食排行榜
- 干无花果怎么吃
- 零食可以托运吗
- C# 8.0 添加和增强的功能【基础篇】
- .NET性能系列文章二:Newtonsoft.Json vs. System.Text.Json
- 某 .NET RabbitMQ SDK 有采集行为,你怎么看?
- .net core Blazor+自定义日志提供器实现实时日志查看器