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

本文关键字:语句 作用域 是什么 using | 更新日期: 2023-09-27 18:13:23

我继承了以下代码:

    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是否具有正确的作用域?

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

从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处置

没有显式花括号的using语句只适用于以下语句:

using (Idisp1)
    // use it
// it's disposed
因此,当链接时,它们以相同的方式工作。这里的第二个using充当单个语句。
using (Idisp1)
    using (Idisp2)
    {
    }

评论者stakx建议格式化,以明确编译器如何读取using块。实际上,这些通常被格式化为遇到的OP:

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块非常满意。

c#语言规范(Version 5)将using语句描述为:

使用叙述:

using ( resource-acquisition ) embedded-statement

:

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

(我强调)

那么,我们最终是如何使用它和花括号呢?因为嵌入式语句的定义是:

embedded-statement:


empty-statement
表达式语句
selection-statement
迭代语句
跳转语句
try语句
checked-statement
unchecked-statement
lock语句
使用叙述
yield语句

:

嵌入式语句非终结符用于出现在其他语句

中的语句
最后,我们发现被定义为:

允许在只允许一条语句的上下文中写入多条语句。

:

{ statement-listopt }

所以基本上,花括号总是可以用于接受单个语句,而不是多个语句的情况。

碰巧的是,几乎总是,我们确实想要使用多个语句,所以花括号往往被视为if, using等语句的一部分。然而,事实上,它们是语言的一个独立部分。

现有的回答分别不正确或不完整。

实际上有两种情况:

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

,它有下一条语句的作用域。

但也有

  • using var resp = req.GetResponse()
  • (注意使用compare to 1后缺少括号)
    具有块作用域,例如当前块或方法。

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

    注意GO中的defer具有FUNCTION作用域,而c#中的using(不带括号)具有BLOCK作用域。

    例子
    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。收集并想要创建另一个记录器,它将抛出System.ObjectDisposedException。

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