
让我们看看博客对C#8.0(Visual Studio 2019 Preview 2版本)中即将进行的更改的评价:
“仅堆栈结构出现在C#7.2中。 它们非常有用,但是与此同时,它们的使用与限制紧密相关,例如,无法实现接口。 现在,可以使用链接结构内部的Dispose方法清除链接结构,而无需使用IDisposable接口。”
就是这样:仅堆栈的ref结构不实现接口,否则将出现打包它们的可能性。 因此,它们不能实现IDisposable,并且我们不能在using语句中使用以下结构:
class Program { static void Main(string[] args) { using (var book = new Book()) { Console.WriteLine("Hello World!"); } } } ref struct Book : IDisposable { public void Dispose() { } }
尝试运行此代码将导致编译错误 :
Error CS8343 'Book': ref structs cannot implement interfaces
但是,现在,如果我们将公共Dispose
方法添加到引用结构,则using
语句将神奇地接受它,并且所有内容都将编译:
class Program { static void Main(string[] args) { using (var book = new Book()) { // ... } } } ref struct Book { public void Dispose() { } }
而且,由于语句本身的变化,您现在可以以更短的形式使用using(即所谓的using
声明):
class Program { static void Main(string[] args) { using var book = new Book(); // ... } }
但是...为什么呢?
这是一个很长的故事,但是总的来说,显式清理(确定性终结)胜于隐式(非确定性终结)。 这很直观。 最好尽快清除资源(通过调用Close,Dispose或使用语句),而不是等待“有一天”(当环境本身启动终结器时)进行的隐式清理。
因此,在创建拥有某种资源的类型时,最好提供显式清理的可能性。 在C#中,这显然是IDisposable
接口及其Dispose
方法。
注意事项 不要忘记,在引用结构的情况下,仅使用显式清理,因为为它们定义终结器是不可能的。
让我们看一下通常的“非托管内存池的包装器”的说明性示例。 正是由于该链接结构旨在为那些对性能痴迷的人们提供帮助,所以它只占用了最小的空间(根本没有使用堆):
public unsafe ref struct UnmanagedArray<T> where T : unmanaged { private T* data; public UnmanagedArray(int length) { data = // get memory from some pool } public ref T this[int index] { get { return ref data[index]; } } public void Dispose() { // return memory to the pool } }
由于包装器包含非托管资源,因此我们在使用后使用Dispose方法对其进行清理。 因此该示例如下所示:
static void Main(string[] args) { var array = new UnmanagedArray<int>(10); Console.WriteLine(array[0]); array.Dispose(); }
这很不方便,因为您需要记住有关调用Dispose的信息。 另外,这是一个痛苦的决定,因为正确处理异常不适用于此处。 因此,为了从内部调用Dispose,引入了using语句。 但是,如前所述,不可能在这种情况下应用它。
但是在C#8.0中,您可以充分利用using语句:
static void Main(string[] args) { using (var array = new UnmanagedArray<int>(10)) { Console.WriteLine(array[0]); } }
同时,由于声明,代码变得更加简洁:
static void Main(string[] args) { using var array = new UnmanagedArray<int>(10); Console.WriteLine(array[0]); }
下面的其他两个示例(为简洁起见,省略了许多代码)取自CoreFX存储库。
第一个示例是ValueUtf8Converter参考结构,该结构包装了数组池中的byte []数组:
internal ref struct ValueUtf8Converter { private byte[] _arrayToReturnToPool; ... public ValueUtf8Converter(Span<byte> initialBuffer) { _arrayToReturnToPool = null; } public Span<byte> ConvertAndTerminateString(ReadOnlySpan<char> value) { ... } public void Dispose() { byte[] toReturn = _arrayToReturnToPool; if (toReturn != null) { _arrayToReturnToPool = null; ArrayPool<byte>.Shared.Return(toReturn); } } }
第二个示例是RegexWriter,它包装了两个需要显式清除的ValueListBuilder引用结构(因为它们还管理数组池中的数组):
internal ref struct RegexWriter { ... private ValueListBuilder<int> _emitted; private ValueListBuilder<int> _intStack; ... public void Dispose() { _emitted.Dispose(); _intStack.Dispose(); } }
结论
可移动引用的结构可以被认为是具有REAL析构函数的低空间类型,例如C ++。 一旦相应的实例超出了using语句的范围(或在using声明的情况下的范围),它将立即被调用。
当然,在编写常规的,面向商业的程序时,它们不会突然流行起来,但是,如果您正在创建高性能,低级代码,则应该了解它们。
我们还有一篇关于会议的文章:
