什么是定义网站中的ref struct

问题描述 投票:3回答:3

我想我前段时间在GitHub中听过“ref like struct”这个词。

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

public ref struct MyStruct
{
    int x;
}

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


引用结构不能用于自动属性或字段。它们也不能被投射到对象上。这些都是实证研究结果。

有了新的c#最近给我的“Span”背景,我猜测ref struct是一个只有堆栈的结构。这是一个永远不会堆的结构。但我不是100%肯定。

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

c# struct ref c#-7.0
3个回答
6
投票

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

此C#功能也称为“内部指针”或“类似于ref的类型”。该提议允许编译器要求某些类型(如Span<T>)仅出现在堆栈中。

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


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

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

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


还有一个official documentation from Microsoft,正如@UnholySheep在评论中指出的那样。


4
投票

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

Microsoft docs on reference semantics in C# 7.2

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

本博客详细解释了这一点:

Adam Sitnik on Span<T>


4
投票

在添加或启用如此标记的值类型本身的任何新功能的意义上,对C#7.2的添加实际上并不是一个特征,而是声明或发布一个特定的限制,该限制管理在其他地方允许使用该类型。

[编辑:在github / dotnet网站上查看span-safety]

因此,不考虑ref struct指定给结构的内容,而是考虑它得到的结果。对外部使用添加任何限制在逻辑上需要ref struct因此假设的相关保证,因此关键字的作用是授权或“许可”ref struct执行需要这些特定保证的事情。

关键在于它是间接的好处,因为通常被ref struct许可的操作类型基本上都不是关键字的关注点,并且可以通过任何地方的wily代码实现和尝试,甚至成功,无论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可以通过将它通过IntPtr往返来使'this'指针不透明。这是留给读者的练习,但一种方法是通过System.​Runtime.​CompilerServices.​Unsafe包。

© www.soinside.com 2019 - 2024. All rights reserved.