“模式化使用”和“使用声明”
注意
本文是特性规范。 此规范是功能的设计文档。 它包括建议的规范变更,以及功能设计和开发过程中所需的信息。 这些文章将持续发布,直至建议的规范变更最终确定并纳入当前的 ECMA 规范。
功能规范与已完成的实现之间可能存在一些差异。 这些差异记录在相关的语言设计会议 (LDM) 说明中。
可以在有关规范的文章中了解更多有关将功能规范子块纳入 C# 语言标准的过程。
支持者问题:https://github.com/dotnet/csharplang/issues/114
总结
该语言将围绕 using
语句添加两项新功能,以便简化资源管理:除了 IDisposable
之外,using
还应识别一次性模式,并为语言添加 using
声明。
动机
using
语句是当今资源管理的有效工具,但需要相当多的工作。 在语法上,管理许多资源的方法可能会因一系列 using
语句而变得复杂。 大多数编码样式指南明确为这种情况的大括号使用提供了例外,因为这种语法负担实在不小。
通过 using
声明,C# 可以简化许多繁琐的过程,使其能够与其他包含资源管理块的语言达到相同的水平。 此外,基于模式的 using
可让开发人员扩展可参与的类型集。 在许多情况下,无需创建仅允许在 using
语句中使用的值的包装器类型。
通过这些功能,开发人员可以简化和扩展 using
的方案。
详细设计
使用声明语法
改语言允许在局部变量声明中添加 using
。 这样的声明与在 using
语句的同一位置声明变量的效果相同。
if (...)
{
using FileStream f = new FileStream(@"C:\source\using.md");
// statements
}
// Equivalent to
if (...)
{
using (FileStream f = new FileStream(@"C:\source\using.md"))
{
// statements
}
}
using
的局部变量的生命周期将延续到其声明所在作用域的末尾。 using
局部变量将按声明的相反顺序释放。
{
using var f1 = new FileStream("...");
using var f2 = new FileStream("...");
using var f3 = new FileStream("...");
...
// Dispose f3
// Dispose f2
// Dispose f1
}
对于 goto
或任何其他控制流构造,在有 using
声明的情况下没有任何限制。 相反,代码的作用与等效的 using
语句完全相同。
{
using var f1 = new FileStream("...");
target:
using var f2 = new FileStream("...");
if (someCondition)
{
// Causes f2 to be disposed but has no effect on f1
goto target;
}
}
在 using
的本地声明中定义的变量将自动是只读的。 这与 using
语句中声明的局部变量的行为一致。
using
声明的语言语法如下:
local-using-declaration:
'using' type using-declarators
using-declarators:
using-declarator
using-declarators , using-declarator
using-declarator:
identifier = expression
有关 using
声明的限制:
- 可能不会直接出现在
case
标签内,但必须出现在case
标签内的块中。 - 不得作为
out
变量声明的一部分出现。 - 每个声明符必须具有初始值。
- 本地类型必须隐式转换为
IDisposable
或满足using
模式。
基于模式的使用
该语言将为 ref struct
类型添加可释放模式的概念,即一种具有可访问 ref struct
实例方法的 Dispose
。 适合可释放模式的类型可以参与 using
语句或声明,而无需实现 IDisposable
。
ref struct Resource
{
public void Dispose() { ... }
}
using (var r = new Resource())
{
// statements
}
这将允许开发人员针对 using
类型利用 ref struct
。 在 C# 8 中,这些类型不能实现接口,因此不能参与 using
语句。
传统 using
语句所施加的相同限制在这里同样适用:在 using
中声明的局部变量是只读的,null
值不会导致抛出异常,等等。代码生成唯一的不同在于,在调用 Dispose 之前不会进行到 IDisposable
的类型转换:
{
Resource r = new Resource();
try {
// statements
}
finally {
if (r != null) r.Dispose();
}
}
为了符合一次性模式,Dispose
方法必须是一个可访问的实例成员,不含参数,并具有 void
返回类型。 它不能是扩展方法。
注意事项
在 C# 8 中,这两方面的考虑都没有实现
不带块的案例标签
using declaration
直接位于 case
标签内是非法的,因为它的实际生命周期存在复杂的问题。 一种潜在的解决方案是简单地赋予它与同一位置的 out var
相同的生存期。 它被认为是功能实现的额外复杂性和工作便利(只是向 case
标签添加一个块)并没有证明采取这条路线是正当的。
未来扩展
固定的局部变量
fixed
语句具有 using
语句的所有属性,这些属性是促成具有 using
局部变量能力的原因。 应考虑将此功能扩展到 fixed
本地对象。 生存期和排序规则应同样适用于此处的 using
和 fixed
。