【问题标题】:What is ref struct in definition site定义站点中的 ref struct 是什么
【发布时间】:2018-06-22 10:08:22
【问题描述】:

我想我前段时间在 GitHub 上听说过一个术语“ref like struct”。

现在我已经掌握了最新的 C# 版本 (7.3),我终于可以自己测试它了。所以这似乎是一个有效的代码:

public ref struct MyStruct
{
    int x;
}

我知道什么是 ref locals 和 ref 返回,因为有相关文档。但我找不到关于 ref struct 的文档。


引用结构不能用于自动属性或字段。它们也不能被强制转换为对象。这些都是实证结果。

在新的 c# 最近给我的“Span”背景下,我猜想 ref struct 是一个仅堆栈的结构。那是一个永远不会进入堆的结构。但我不是 100% 确定。

我很确定应该有关于此的文档,但我没有找到它。

【问题讨论】:

  • 是的。但是这里的 ref 一词似乎有不同的含义。 struct 不一定通过引用传递(我首先想到的),但它似乎强制 struct 仅驻留在堆栈上。
  • 表示struct直接访问托管内存,必须一直栈分配。这是 C# 7.2 中引入的新特性。
  • 它只是编译代码限制的关键字,有助于限制编码规则。你可能认为是一个好主意,当你的想法在现实世界中执行时,世界注定会陷入核冬天;)

标签: c# struct ref c#-7.0


【解决方案1】:

经过一番研究,我偶然发现了Compile time enforcement of safety for ref-like types in C# 7.2上的这篇文章。

此 C# 功能也称为“内部指针”或“类引用类型”。提议是允许编译器要求某些类型如Span<T> 只出现在堆栈上。

该网站还说明了这样做的好处,主要涉及垃圾收集和堆栈分配。


使用类似 ref 的类型也会带来一些限制,例如:

  • 类似ref的类型不能是数组元素的类型
  • 类 ref 类型不能用作泛型类型参数
  • 类引用变量不能装箱
  • 类引用类型不能是普通非类引用类型的字段
  • 类似 ref 的类型不能实现接口
  • 间接限制,例如不允许在异步方法中使用类似 ref 的类型,这实际上是不允许使用类似 ref 的类型字段的结果。

这限制了它们用于参数、局部变量,在某些情况下还用于返回值。


也存在official documentation from Microsoft,正如@UnholySheep 在 cmets 中指出的那样。

【讨论】:

    【解决方案2】:

    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 消失(我认为这是一个错误,请参阅herehere)。

    因为 ref struct 代码应该被正确地允许返回它自己的 this 仍然被编译器阻止这样做,所以你必须求助于一些相当粗暴的技术来绕过编码时的致命错误在所谓的解放 ref struct 实例方法中。也就是说,用 C# 编写的值类型实例方法合法地需要覆盖致命错误CS8170​/​CS8157 可以逐轮不透明“this”指针 - 通过IntPtr 将其绊倒。这留给读者作为练习,但一种方法是通过System.​Runtime.​CompilerServices.​Unsafe 包。

    【讨论】:

    • 不错的答案,特别是 “不是真正的功能” 部分,而是 “特定限制”
    • 这与 stackalloc 有何不同?
    • @Brian stackalloc 是一种动态从堆栈中动态分配临时内存范围的方法。 stackalloc 的元素类型必须是原始类型,因此这与 ref struct 没有直接关系,除非您(再次“粗暴地”)叠加/覆盖/暗示一个或多个 ref struct 实例stackalloc(例如)字节范围的顶部,您已安排足够大以容纳它(或它们)。
    【解决方案3】:

    只是在其他答案中添加一点。基本上,他们创建了一个 ref 结构,以便能够将托管指针作为成员保存。这意味着它不能被垃圾回收,如果它最终在堆上,GC 就会崩溃。对你能做什么和不能做什么的奇怪限制与此有关(如此处的微软文档中所述):

    Microsoft docs on reference semantics in C# 7.2

    所有这些都非常吸引人,但并没有真正解释为什么他们提供了这个功能。真正的原因是允许同时处理托管和非托管内存的 api 拥有一个通用接口(即消除对无限重载的需要)。

    这在这篇博客中有详细解释:

    Adam Sitnik on Span<T>

    【讨论】:

    • 这个名字真的很误导人。为什么叫 ref?它与通过引用传递结构有什么关系吗?如果不是,我们是否应该在概念上将其视为“stackonly”?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-21
    • 1970-01-01
    • 1970-01-01
    • 2021-10-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多