C#中“using”的用途是什么?

356

用户kokos通过提到using关键字回答了精彩的问题《C#的隐藏特性》。你能详细说明一下吗?using有哪些用途?


这是支持RAII习惯的C#方式:http://www.hackcraft.net/raii/ - Nemanja Trifunovic
1
您可以使用已实现IDispose接口的对象。当该对象超出范围时,Using语句将调用Dispose方法。它保证即使发生任何异常也会调用Dispose。它的工作方式类似于finally子句并执行Dispose。 - CharithJ
29个回答

534
< p >使用using语句的原因是确保对象在超出范围时立即被处理,而不需要显式代码来确保发生这种情况。

就像在理解C#中的'using'语句(codeproject)使用实现IDisposable的对象(microsoft)中一样,C#编译器会转换

using (MyResource myRes = new MyResource())
{
    myRes.DoSomething();
}

{ // Limits scope of myRes
    MyResource myRes= new MyResource();
    try
    {
        myRes.DoSomething();
    }
    finally
    {
        // Check for a null resource.
        if (myRes != null)
            // Call the object's Dispose method.
            ((IDisposable)myRes).Dispose();
    }
}

C# 8引入了一种名为“using declarations”的新语法:
使用声明是一个以using关键字开头的变量声明。它告诉编译器,在封闭作用域结束时,应该处理被声明的变量。
因此,上述代码的等效代码如下:
using var myRes = new MyResource();
myRes.DoSomething();

当控制权离开包含范围(通常是一个方法,但也可以是代码块)时,myRes将被处理。


143
请注意,这不一定是关于对象被“正确”处理的问题,而更多的是它是否及时被处理。实现IDisposable接口并持有非托管资源(如流和文件句柄)的对象还将实现终结器,以确保在垃圾回收期间调用Dispose。问题是,垃圾回收可能需要相对较长的时间。using 语句可以确保在使用完对象后调用 Dispose 方法。 - John Saunders
1
请注意,当MyRessource是一个结构体时,生成的代码会有所不同。显然没有对空值进行测试,也没有装箱到IDisposable。将发出一个受限制的虚拟调用。 - Romain Verdier
7
为什么没有人提到“using”也可以用来导入命名空间? - Kyle Delaney
3
请注意,如果您直接编写第二个版本的代码,则结果不同。如果使用 using ,则在其内部构建的变量是只读的。没有办法在没有 using 语句的情况下对局部变量实现这一点。 - Massimiliano Kraus
对于来自Python世界的人来说,这是否相当于类似于with open(filename) as f: # blah的东西? - Chromium
3
@JohnSaunders 另外,终结器不能保证被调用。 - Pablo H

138

由于仍有很多人这样做:

using (System.IO.StreamReader r = new System.IO.StreamReader(""))
using (System.IO.StreamReader r2 = new System.IO.StreamReader("")) {
   //code
}

我猜很多人还不知道你可以这样做:

using (System.IO.StreamReader r = new System.IO.StreamReader(""), r2 = new System.IO.StreamReader("")) {
   //code
}

2
在一个using语句中使用多个不同类型的对象是否可行? - Agnel Kurian
14
@AgnelKurian No: "error CS1044: Cannot use more than one type in a for, using, fixed or declaration statement"翻译:@AgnelKurian 不行:“错误 CS1044:在 for、using、fixed 或声明语句中不能使用多个类型”。 - David Sykes
21
这个回答如何回答这个问题? - Liam
我其实不知道我可以在一个代码块前写两个using语句(以前每次都会嵌套它们)。 - kub1x

100

这样的事情:

using (var conn = new SqlConnection("connection string"))
{
   conn.Open();

    // Execute SQL statement here on the connection you created
}

即使出现异常,也无需使用try/catch/finally块,此SqlConnection将在不需要显式调用.Close()函数的情况下关闭。


1
如果我在方法内使用了 "using",并且在 using 中间返回,会有什么问题吗? - francisco_ssb
1
这没有问题。在这个例子中,即使你从using块的中间return,连接仍然会被关闭。 - Joel Coehoorn

32

using可以用来调用IDisposable。它还可以用于给类型起别名。

using (SqlConnection cnn = new SqlConnection()) { /* Code */}
using f1 = System.Windows.Forms.Form;

Not in this order though ;) - derHugo

22

using,在技术语境下指

using (var foo = new Bar())
{
  Baz();
}

实际上是try/finally块的简写。它等同于以下代码:

var foo = new Bar();
try
{
  Baz();
}
finally
{
  foo.Dispose();
}

当然,您会注意到第一个代码片段比第二个更加简洁,并且即使抛出异常,您可能仍然想要进行许多种清理操作。因此,我们提出了一种称为Scope的类,它允许您在Dispose方法中执行任意代码。例如,如果您有一个名为IsWorking的属性,在尝试执行操作后始终希望将其设置为false,您可以这样做:

using (new Scope(() => IsWorking = false))
{
  IsWorking = true;
  MundaneYetDangerousWork();
}

您可以在这里了解我们的解决方案及其来源。


12

Microsoft文档说明using具有双重功能(https://msdn.microsoft.com/zh-cn/library/zhdeatwt.aspx),既可作为指令,也可用于语句。作为语句,正如其他答案中所指出的那样,该关键字基本上是一种语法糖,用于确定处置IDisposable对象的范围。作为指令,它通常用于导入命名空间和类型。同时作为指令,你可以为命名空间和类型创建别名,就像Joseph和Ben Albahari在书籍《C# 5.0 In a Nutshell: The Definitive Guide》(http://www.amazon.com/5-0-Nutshell-The-Definitive-Reference-ebook/dp/B008E6I1K8)中所指出的那样。以下是一个例子:

namespace HelloWorld
{
    using AppFunc = Func<IDictionary<DateTime, string>, List<string>>;
    public class Startup
    {
        public static AppFunc OrderEvents() 
        {
            AppFunc appFunc = (IDictionary<DateTime, string> events) =>
            {
                if ((events != null) && (events.Count > 0))
                {
                    List<string> result = events.OrderBy(ev => ev.Key)
                        .Select(ev => ev.Value)
                        .ToList();
                    return result;
                }
                throw new ArgumentException("Event dictionary is null or empty.");
            };
            return appFunc;
        }
    }
}

这是需要明智采纳的做法,因为滥用这种做法可能会损害代码的清晰度。在DotNetPearls有一个很好的C#别名解释,还提到了其优缺点(http://www.dotnetperls.com/using-alias)。


5
说实话:我讨厌使用using作为别名工具。当阅读代码时,它会让我感到困惑-- 我已经知道System.Collections存在并且有IEnumerable<T>类。使用别名将其命名为其他名称会使其对我变得模糊不清。例如,我看到using FooCollection = IEnumerable<Foo>可能会让后来的开发人员阅读代码时想,“什么是FooCollection,为什么没有相应的类?”我从不使用它,并且会劝阻其使用。但这可能只是我的个人观点。 - Ari Roth
1
附言:我承认偶尔可能会有用处,比如您在其中使用它来定义委托。但我认为这些情况相对较少。 - Ari Roth

10

我过去常常使用它来处理输入输出流。你可以很好地嵌套它们,并且它消除了通常会遇到的许多潜在问题(通过自动调用dispose)。例如:

        using (FileStream fs = new FileStream("c:\file.txt", FileMode.Open))
        {
            using (BufferedStream bs = new BufferedStream(fs))
            {
                using (System.IO.StreamReader sr = new StreamReader(bs))
                {
                    string output = sr.ReadToEnd();
                }
            }
        }

8

我想补充一点,我很惊讶这个问题没有被提到。在我看来,使用语句最有意思的功能是无论你如何退出使用块,它都会自动释放对象,包括返回和异常。

using (var db = new DbContext())
{
    if(db.State == State.Closed)
        throw new Exception("Database connection is closed.");
    return db.Something.ToList();
}

无论是抛出异常还是返回列表,DbContext对象都会被释放。

6

使用 using 的另一个伟大用途是在实例化模态对话框时。

Using frm as new Form1

    Form1.ShowDialog

    ' Do stuff here

End Using

3
你的意思是 frm.ShowDialog 吗? - StayOnTarget
1
这个问题是关于C#的。在VB.NET中它是否也能够工作,或者只是看起来像而已?它是在哪个版本的VB.NET(以及相应版本的Visual Studio)中引入的? - Peter Mortensen

5
您可以通过以下示例使用别名命名空间:

using LegacyEntities = CompanyFoo.CoreLib.x86.VBComponents.CompanyObjects;

这被称为使用别名指令,正如你所看到的,它可以用于隐藏冗长的引用,如果你想在代码中明确表示你正在引用的内容,例如:

LegacyEntities.Account

替代

CompanyFoo.CoreLib.x86.VBComponents.CompanyObjects.Account

或者简单地说
Account   // It is not obvious this is a legacy entity

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