没有花括号的using语句的作用域是什么?

52

我继承了以下代码:

    using (var dataAccessConnection = da.GetConnection()) //no opening curly brace here
    using (var command = new SqlCommand(sql, dataAccessConnection.Connection))
    {
        command.CommandType = CommandType.Text;
        using (var sqlDataReader = command.ExecuteReader(CommandBehavior.CloseConnection))
        {
            while (sqlDataReader.Read())
            {
                rueckgabe.Add(new Myclass
                                  {
                                      Uid = Guid.NewGuid(),
                                      ImportVersionUid = versionUid,
                                      MyProperty = Convert.ToInt32(sqlDataReader["MyProperty"])
                                        });       
            }
        }
        command.Connection.Close();
        dataAccessConnection.Connection.Close();
    }

看代码,我期望在using子句之后有一个左花括号。

代码可以编译,并且做了它应该做的事情。应用程序行为不可预测。有时它无法访问数据库服务器。

这段代码有意义吗?dataAccessConnection的范围是否正确?


C#中的嵌套using语句 - Soner Gönül
链接中的一位评论者说:“看起来第一个using语句是空的,没有使用。” 这也是我遇到的问题。但现在我看到这实际上是一种简洁的风格。 - Mathias F
"using" 的使用方式与 "if" 相同。即使我个人使用大括号,如果后面只跟一个元素,它也可以不用 "if"。 - Ole Albers
4个回答

78

从 C# 8.0 开始,using 关键字可用作一次性对象变量声明中的属性(参考文献)。语义与您预期的相同--对象在范围结束时自动处理。

        public class Disposable : IDisposable
        {
            string name;

            public Disposable(string name)
            {
                this.name = name;
            }

            public void Dispose()
            {
                Console.WriteLine(name + " disposed");
            }

            public void Identify()
            {
                Console.WriteLine(name);
            }

            static void Main(string[] args)
            {
                using Disposable d1 = new Disposable("Using 1");
                Disposable d2 = new Disposable("No Using 2");
                using Disposable d3 = new Disposable("Using 3");
                Disposable d4 = new Disposable("No Using 4");
                d1.Identify();
                d2.Identify();
                d3.Identify();
                d4.Identify();
            }
        }

输出

使用 1
不使用 2
使用 3
不使用 4
使用 3 已释放
使用 1 已释放

6
那么,也许我漏掉了什么,但这有什么意义?在函数或方法中声明一个变量并使用此方法不会得到相同的结果吗? - Arvo Bowen
3
我认为将静态的 Main 方法移出 Disposable 类会使示例更容易理解。 - Andrius R.
7
这是不错的信息,但是2014年的问题没有使用C# 8功能!请注意using后面是否跟着( ... )。例如,像你例子中的using Disposable d1 = new Disposable("Using 1");语句声明了d1变量,该变量在方法(或我们所在的任何块)的其余部分中都处于作用域,并且在控制离开时将被处理。相比之下,using (Disposable d1 = new Disposable("Using 1"));是旧式语法,其中变量d1的作用域仅为最后看到的空语句;!这里的;就像{ } - Jeppe Stig Nielsen
6
尽管这个回答中的信息可能是人们今天通常寻找的内容,但2014年问题的真正解决方案可以在其他答案中看到。第一个 using 语句的作用范围是紧随其后的(嵌套的)using 语句。 - Jeppe Stig Nielsen
为什么3在1之前被处理?它是否自行升至第一个声明的位置? - Tom

47

using语句没有明确的大括号,仅适用于下一条语句。

using (Idisp1)
    // use it

// it's disposed

因此,当它们被链接在一起时,它们的工作方式相同。第二个using在这里充当一个单独的语句。

using (Idisp1)
    using (Idisp2)
    {

    }

评论者stakx建议对代码进行格式化以清晰地展示编译器如何读取using块。实际上,这些通常会按照原帖中遇到的格式进行排版:

using (Idisp1)
using (Idisp2)
{

}

那相当于这个:

using (Idisp1)
{
    using (Idisp2)
    {

    }
}

请注意,位于顶部的第一个始终是最后一个处理的。因此,在所有以前的示例中,Idisp2.Dispose()Idisp1.Dispose() 之前被调用。在许多情况下,这并不重要,比如读取网页时,但我认为您应该始终意识到您的代码将要做什么,并做出知情的决定。

例如,在读取网页时就是这种情况:

HttpWebRequest req = ...;

using (var resp = req.GetResponse())
using (var stream = resp.GetResponseStream())
using (var reader = new StreamReader(stream))
{
    TextBox1.Text = reader.ReadToEnd(); // or whatever
}
我们获取响应,获取流,获取读取器,读取流,释放读取器,释放流,最后释放响应。
请注意,正如评论者Nikhil Agrawal指出的那样,这是与块相关的语言特性,不仅适用于using关键字。例如,对于if块也是一样的。
if (condition)
    // may or may not execute

// definitely will execute

对抗

if (condition1)
    if (condition2)
       // will execute if both are true

// definitely will execute

虽然当然不应该这样使用if语句,因为这样太难读懂了,但我认为这可以帮助你理解using的用例。个人而言,我非常喜欢链接using块。


2
更准确地说,我会说它们适用于以下语句(正如您所观察到的那样,该语句实际上可能跨越多行)。 - Damien_The_Unbeliever
@Damien_The_Unbeliever 对的!那就是我要找的词。我会编辑我的回答。谢谢。 - Matthew Haugen
@MatthewHaugen:我认为嵌套的if-else块比嵌套的if块更有趣,因为大多数人感觉在if(foo){}else{if(bar){}}的else块中省略花括号是完全可以接受的。 - Brian
2
@MatthewHaugen - 自C# 8.0以来,情况已经发生了变化,对吧? - Homer
6
@ Homer,是和不是——肯定有新的东西加入了,但我在这里说的仍然是真实的。 "简单使用语句" 没有圆括号。因此, using (var resp = req.GetResponse()) 仍然仅限于下一行的范围,但 using var resp = req.GetResponse() 将一直存在到块结束。下次我有机会时会进行编辑。 - Matthew Haugen
显示剩余3条评论

3
C#语言规范(版本5)将using语句描述为:

using-statement:

using ( resource-acquisition ) embedded-statement

也就是说:

using语句获取一个或多个资源,执行一个语句,然后释放该资源。

我的强调
那么,我们为什么要使用花括号呢?因为embedded-statement的定义是:

嵌入语句:


空语句
表达式语句
选择语句
迭代语句
跳转语句
尝试语句
检查语句
未经检查的语句
锁定语句
使用语句
yield语句

而:

embedded-statement非终结符用于出现在其他语句中的语句。

最后,我们发现的定义如下:

允许在允许单个语句的上下文中编写多个语句。

{ statement-listopt }

基本上,花括号可以用来处理只接受一个语句的情况,并改为接受多个语句。
事实上,我们几乎总是需要使用多个语句,所以花括号通常被视为if、using等语句的一部分。然而,它们实际上是语言的一个单独部分。

3

现有的响应分别不正确或不完整。

实际上有两种情况:

  1. using (var resp = req.GetResponse())

它具有下一个语句的范围。

但也有

  1. using var resp = req.GetResponse()

(请注意,与1相比,在using后缺少括号)
它具有块作用域,例如当前块或方法。

因此,如果您在方法的根块内使用using var resp = req.GetResponse(),则使用变量将具有方法作用域,实际上类似于go中的defer语句。

请注意,GO中的defer具有函数作用域,而C#中的using(无括号)具有块作用域。

例子

public static ILoggerFactory TestLogging()
{
    // dotnet add package Microsoft.Extensions.Logging
    // dotnet add package OpenTelemetry.Exporter.Console

    using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
    {
        builder.AddOpenTelemetry(options =>
        {
            options.AddConsoleExporter();
        });
    });

    ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
    System.GC.Collect();
    var fac = loggerFactory.CreateLogger<Program>();
    System.Console.WriteLine(fac);


    logger.LogInformation(eventId: 123, "Hello from {name} {price}.", "tomato", 2.99);

    if (logger.IsEnabled(LogLevel.Debug))
    {
        // If logger.IsEnabled returned false, the code doesn't have to spend time evaluating the arguments.
        // This can be especially helpful if the arguments are expensive to calculate.
        logger.LogDebug(eventId: 501, "System.Environment.Version: {version}.", System.Environment.Version);
    }

    return loggerFactory;
}

在函数内部,您可以使用loggerFactory在调用GC.Collect()后创建第二个记录器。

但是当您从函数返回loggerFactory时,调用GC.Collect并想要创建另一个记录器时,它会抛出System.ObjectDisposedException异常。

var x = TestLogging();
System.GC.Collect();
var log = x.CreateLogger<Program>(); // throws ObjectDisposedException

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接