何时处置“using var”?是尽快超出范围还是在块结束时?

When is a `using var` disposed? Is it out-of-scope as soon as possible or at the end of the block?

提问人:geekley 提问时间:1/5/2023 更新时间:1/5/2023 访问量:636

问:

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using#using-declaration

本地的生存期将延长到声明它的作用域的末尾。然后,当地人将按照他们申报的相反顺序进行处置。usingusing

我的问题是:什么时候本地被认为是超出范围的?
它一定是在区块的尽头吗?
它是否一定是在块中最后一次使用后就对的?
或者它是实现定义的,因此它可以是其中之一,甚至可以介于两者之间?
using

换言之:

{
  using var res = Res();
  res.DoStuff();
  somethingElse.DoWhatever();
  res.DoMoreStuff();
  // 100 more statements that have nothing to do with res
}

这总是等价于这个(1)吗?

{
  using (var res = Res()) {
    res.DoStuff();
    somethingElse.DoWhatever();
    res.DoMoreStuff();
    // 100 more statements that have nothing to do with res
  }
}

还是总是这样(2)?

{
  using (var res = Res()) {
    res.DoStuff();
    somethingElse.DoWhatever();
    res.DoMoreStuff();
  }
  // 100 more statements that have nothing to do with res
}

或者这是一个实现细节?
规范是否对此进行了定义?从技术上讲,这样的“范围”是什么?如果上述情况之一总是如此,那么是否有理由更喜欢这种行为而不是另一种行为?我认为(2)更好,但也许我错了。

我知道对于高级编程来说,它可能没有太大变化,但我很好奇。


有点相关:using 块是否保证在块结束之前不会释放对象?

使用 IDisposable using-statement C# 作用域

评论

1赞 Ňɏssa Pøngjǣrdenlarp 1/5/2023
大括号和缩进表示范围
0赞 Scott Hannen 1/5/2023
最简单的分辨方法 - 尝试参考它。如果它编译,则它在范围内。如果没有,那就不是。
3赞 geekley 1/5/2023
我想我的问题有点令人困惑。我知道该变量在“编辑时”仍处于“范围内”。我的问题更多的是编译器是否可以提前“优化”和“终止”其“运行时”范围(导致它可能在块结束之前处置),因为我不再引用它。我之所以问,是因为它可以做到这一点,并且代码将是“功能等效的”——除非规范实际上要求此语法意味着必须在范围/块的末尾添加 finally/dispose。
0赞 scharnyw 8/29/2023
使用这种语言功能时,我的脑海中总是会出现这种疑问,我讨厌它似乎没有在任何地方明确指定。特别是因为编译器/运行时不能明确保证在声明块结束之前保持局部变量处于活动状态。

答:

3赞 Sweeper 1/5/2023 #1

根据语言规范,这就是 Scope 的含义:

名称的范围是程序文本的区域,在该区域中可以引用由名称声明的实体,而无需对名称进行限定。

[...]

在local_variable_declaration中声明的局部变量的作用域 (§12.6.2) 是发生声明的

请注意,声明只是添加到局部变量声明中的单词。根据使用声明提案,usingusing

该语言将允许添加到局部变量声明中。using

因此,您的声明等同于using

{
  using (var res = Res()) {
    res.DoStuff();
    somethingElse.DoWhatever();
    res.DoMoreStuff();
    // 100 more statements that have nothing to do with res
  }
}

最外层表示一个块{ ... }

评论

0赞 geekley 1/5/2023
好的,但是这真的能保证语法添加的隐式 Dispose 只会在 100 个语句之后调用吗?我不相信编译器不可能将其“优化”为 (2),即可能在块结束之前但在我不再引用它之后调用 Dispose(它在功能上等同于“超出范围”)。在实际规范中是否有隐式 try-finally 真正延伸到整个块末尾的保证?using var
0赞 geekley 1/5/2023
我说“实际规格”是因为我不确定问题中链接的页面是否是“最终”/最终规格......它奇怪地按 C# 版本拆分,并且在 URL 中有“提案”,它们看起来更像是草稿。但是我找不到其他任何东西,所以也许它们是整个正式规范......?
1赞 Sweeper 1/5/2023
@geekley目前没有“实际规格”,只有草稿。他们仍在为 C# 7 编写规范,此功能在 C# 8 中。请耐心等待:)至于在区块结束前不移动电话,提案中的措辞几乎是你现在能得到的保证。这不是优化。从 (1) 更改为 (2) 会完全改变程序行为。想想如果抛出异常会发生什么。Disposeres.DoStuff();
0赞 geekley 1/13/2023
啊,我明白了。因此,规范保证如果抛出异常,100 条语句将不会运行。感谢您澄清 (1) 和 (2) 在功能上是不同的。现在这很有意义!
0赞 geekley 3/9/2023
不,等等。现在我很困惑。我真的认为 (1) 和 (2) 在功能上是等价的。如果抛出异常,则在这两种情况下都会发生相同的情况:100 个语句不会以任何一种方式运行(无论是在 Dispose 之前还是在之后),因为这并不意味着 .唯一的区别是,在成功执行时,(2) 调用 Dispose earlier no?你能解释一下你的意思吗?usingcatch
0赞 T.S. 1/5/2023 #2

另外,关于

然后,当地人将按照他们申报的相反顺序进行处置。using

using (var v1 = Class1()) // disposed third
using (var v2 = Class2()) // disposed second
using (var v3 = Class3()) // disposed first
{
  . . . .
}
1赞 tymtam 1/5/2023 #3

在 .NET 博客的 Do more with patterns in C# 8.0 中,我们可以阅读:

using 声明只是前面带有 using 关键字的局部变量声明,其内容在当前语句块的末尾进行处理。

然后我们可以看到:

static void Main(string[] args)
{
    using var options = Parse(args);
    if (options["verbose"]) { WriteLine("Logging..."); }

} // options disposed here