“模式化使用”和“使用声明”

注意

本文是特性规范。 此规范是功能的设计文档。 它包括建议的规范变更,以及功能设计和开发过程中所需的信息。 这些文章将持续发布,直至建议的规范变更最终确定并纳入当前的 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 本地对象。 生存期和排序规则应同样适用于此处的 usingfixed