C# 7.2 的这一新增功能并不是真正的功能,因为它可以添加或启用任何具有标记值的新功能-type 本身,相反,它允许开发人员声明或发布特定的限制,该限制管理该类型在其他任何地方的允许使用。
[编辑:参见 github/dotnet 站点上的span-safety]
因此,与其考虑ref struct 指定给结构的最终用户带来了什么,不如考虑它对作者的好处。对外部使用添加任何限制在逻辑上需要ref struct 假设的相关保证,因此关键字的效果是授权或“许可”ref struct 做需要那些特定的事情保证。
关键是它是一个间接的好处,因为通常认为由ref struct 许可的操作类型基本上与关键字无关,并且可以实施和尝试,也许会成功甚至,无论ref struct 标记如何(或没有),都可以在任何地方使用巧妙的代码。
理论部分就讲这么多。实际上,什么是“狡猾的代码”用例,它如此存在地依赖于额外的保证,甚至到了接受所有伴随限制的极端程度?从本质上讲,struct 能够公开对其自身的托管引用或其字段之一。
通常,C# 会对 this 引用从 struct 的任何实例方法中泄漏出来实施严格的限制:
error CS8170: Struct members cannot return 'this' or other instance members by reference
编译器必须确定this 几乎不可能泄漏出值类型,因为有可能(在某些用途中,很有可能)结构实例已被临时装箱以用于调用实例方法,在这种情况下,将没有持久的GetPinnableReference 实例,相对于该实例可能会采用指向struct(或其内部)的托管指针。
随着近年来对ref 的所有增强,C# 现在更加努力地检测和禁止this 转义。例如,除了上述之外,我们现在还有:
error CS8157: Cannot return 'x' by reference because it was initialized to a value that cannot be returned by reference
..以及相关错误,例如...
error CS8374: Cannot ref-assign 'foo' to 'p' because 'foo' has a narrower escape scope than 'p'.
有时编译器断言CS8157 的根本原因可能令人费解或难以看出,但编译器遵循保守的“比抱歉更安全”的方法,这有时会导致误报,例如,您有额外的特殊知识,即转义最终包含在堆栈中。
对于CS8157 确实没有根据的情况(即,考虑到编译器无法推断的信息),这些代表了我之前提到的“'也许甚至成功'的狡猾代码”示例,并且通常没有简单的解决方法,尤其是通过ref struct。这是因为复杂的误报通常只出现在更高级别的 ref 传递代码场景中,这些场景永远无法采用最初为 ref struct 实施的极端限制。
相反,ref struct 用于非常简单的值类型。通过保证他们的this 引用将始终锚定在上层堆栈框架中——因此至关重要的是,它们永远不会在 GC 堆中泛滥——这样的类型因此获得了发布指向它们自己或其内部的托管指针的信心。
但是请记住,我说过ref struct 不知道它提供的放松的方式、原因和用途。我特别提到的是,不幸的是,使用ref struct 不会使CS8157 消失(我认为这是一个错误,请参阅here 和here)。
因为 ref struct 代码应该被正确地允许返回它自己的 this 仍然被编译器阻止这样做,所以你必须求助于一些相当粗暴的技术来绕过编码时的致命错误在所谓的解放 ref struct 实例方法中。也就是说,用 C# 编写的值类型实例方法合法地需要覆盖致命错误CS8170/CS8157 可以逐轮不透明“this”指针 - 通过IntPtr 将其绊倒。这留给读者作为练习,但一种方法是通过System.Runtime.CompilerServices.Unsafe 包。