使用语句 vs IDisposable.Dispose()

61

据我所知,.NET中的using语句在代码块退出时调用IDisposable对象的Dispose()方法。

using语句还有其他作用吗?如果没有,那么以下两个代码示例将实现完全相同的功能:

Using Con as New Connection()
    Con.Open()
    'do whatever '
End Using

Dim Con as New Connection()
Con.Open()
'do whatever '
Con.Dispose()

只要有人确认我正确或指出我错误并解释原因,我就会给出最佳答案。请注意,某些类在它们的 Dispose() 方法中可以执行不同的操作。这个问题是关于使用using语句是否可以达到调用对象的Dispose()方法的完全相同结果。


这是非常好的建议。我的问题不同,但我一直在使用USING语句,因为它具有出色的内存管理特性。干杯! - pianocomposer
9个回答

88

using基本上相当于:

try
{
  // code
}
finally
{
  obj.Dispose();
}

即使在代码块中抛出未处理的异常,使用它还可以调用Dispose()


是的,我知道我发布的是C#而不是VB,但希望你能翻译 :-) - Brian Warshaw
C#是我首选的.NET语言,但我在VB.NET应用程序中输入了我的示例。此外,这个示例和答案正是我正在寻找的。 - oscilatingcretin
1
在调用 obj.Dispose() 之前,应该对 obj 进行空值检查。由于异常被抛出,可能根本没有为其分配任何值。 - Eric J.
3
@EricJ.,这个答案并不是一个解决编程问题的方案——它只是对使用using语句和代码块时发生的事情进行了基本的说明。 - Brian Warshaw
它还会在持有连接的对象上调用close()方法。 - jnoreiga

21

Brian Warshaw这里所述,使用using块只是一种实现tryfinally块的方法,以确保对象被处理。除了他的回答之外,using块还确保即使你在scope中使用return语句,对象也会被处理。

我曾经对此感到好奇,使用以下方法进行测试:

自定义可处理类和主函数

private class DisposableTest : IDisposable
{
    public string Name { get; set; }

    public void Dispose() { Console.WriteLine("{0}.Dispose() is called !", Name); }
}

public static void Main(string[] args)
{
    try
    {
        UsingReturnTest();
        UsingExceptionTest();                
    }
    catch { }

    try
    {
        DisposeReturnTest();
        DisposeExceptionTest();                
    }
    catch { }

    DisposeExtraTest();

    Console.ReadLine();
}        

测试用例实现

private static string UsingReturnTest()
{
    using (DisposableTest usingReturn = new DisposableTest() { Name = "UsingReturn" })
    {
        return usingReturn.Name;
    }
}

private static void UsingExceptionTest()
{
    using (DisposableTest usingException = new DisposableTest() { Name = "UsingException" })
    {
        int x = int.Parse("NaN");
    }
}

private static string DisposeReturnTest()
{        
    DisposableTest disposeReturn = new DisposableTest() { Name = "DisposeReturn" };
    return disposeReturn.Name;
    disposeReturn.Dispose(); // # IDE Warning; Unreachable code detected
}

private static void DisposeExceptionTest()
{
    DisposableTest disposeException = new DisposableTest() { Name = "DisposeException" };
    int x = int.Parse("NaN");
    disposeException.Dispose();
}

private static void DisposeExtraTest()
{
    DisposableTest disposeExtra = null;
    try
    {
        disposeExtra = new DisposableTest() { Name = "DisposeExtra" };
        return;
    }
    catch { }
    finally
    {
        if (disposeExtra != null) { disposeExtra.Dispose(); }
    }
}

输出结果为:

  • 调用UsingReturn.Dispose()!
  • 调用UsingException.Dispose()!
  • 调用DisposeExtra.Dispose()!

10
//preceeding code
using (con = new Connection()) {
    con.Open()
    //do whatever
}
//following code

等效于以下内容(请注意con的范围受限):

//preceeding code
{
    var con = new Connection();
    try {
        con.Open()
        //do whatever
    } finally {
        if (con != null) con.Dispose();
    }
}
//following code

这里有详细介绍:http://msdn.microsoft.com/zh-cn/library/yh598w02.aspx

using语句确保即使在调用对象方法时发生异常,也会调用Dispose方法。实际上,编译器将using语句转换成在try块中放置对象,然后在finally块中调用Dispose的形式,以实现相同的效果。


6
使用using语句比try...finally{Dispose()}结构更清晰、更简洁,应该在几乎所有不希望块在没有调用Dispose的情况下退出的情况下使用。唯一常见的手动释放比较好的情况是:
  1. 一个方法调用一个工厂方法,返回可能实现或不实现`IDisposable`的内容,但如果它实现了,则需要进行处理(这种情况会出现在非泛型的`IEnumerable.GetEnumerator()`中)。设计良好的工厂接口应该返回一种实现`IDisposable`的类型(可能具有无操作实现,就像`IEnumerator`通常的情况一样),或者指定调用者不需要对返回的对象进行处理。不幸的是,有些接口,比如非泛型的`IEnumerable`,既不能满足第一个标准也不能满足第二个标准。请注意,在这种情况下不能很好地使用using,因为它仅适用于已声明类型实现`IDisposable`的存储位置。
  2. 预期IDisposable对象将在块退出后继续存在(通常发生在设置IDisposable字段或从工厂方法返回IDisposable时)。

请注意,当从工厂方法返回IDisposable时,应使用以下内容:

  bool ok = false;
  DisposableClass myThing;
  try
  {
    myThing = new DisposableClass();
    ...
    ok = true;
    return myThing;
  }
  finally
  {
    if (!ok)
    {
      if (myThing != null)
        myThing.Dispose();
    }
  }

以确保在未返回myThing的情况下对其进行处理。我希望可以使用using和一些“取消Dispose”的方法,但没有这样的方法存在。


我在我的代码中的几个地方使用了这种变体。我使用catch -> dispose -> throw而不是finally和布尔值。您是否有任何信息,它们是否等效或其中一个比另一个更好? - julealgon
我喜欢在finally中进行这样的清理,有几个原因:(1)如果一些无条件的清理代码应该在仅失败时清理代码之前运行,这样的顺序将很好地适合模式[只需将其放在if(!ok)之前即可;(2)捕获并重新抛出异常的语义与不捕获它是不同的。除其他事项外:(1)捕获并重新抛出异常将导致所有内部finally块在异常被重新抛出之前运行。如果“try,catch和rethrow”块包含对同一方法的两个调用,并且其中一个引发异常... - supercat
堆栈跟踪将显示重新抛出的行号,而不是调用失败的方法的行号。此外,如果外部块使用异常筛选器(在VB.NET中可能,在下一个版本的C#中可能),那么在该筛选器中设置断点将使得在堆栈展开之前检查系统状态成为可能。这在某些代码部分中某些异常会被半期望(因此不应停止调试器)但在其他部分中不会(因此应该停止)的情况下非常有帮助。 - supercat
1
@SamusArin:GC不会调用Dispose;它可能会调用Finalize,但这通常被认为是不可靠的。通常的正确模式是确保对于每个IDisposable对象,有一个可以清晰识别的实体负责在其上调用Dispose,或者有一个活动执行上下文将在退出时调用Dispose。在上述工厂函数退出之前,它将有责任调用Dispose;一旦ok设置为true,调用方将有责任。 - supercat
1
说起来可能更清晰的方式是,除了一个问题之外,调用者总是有责任:如果工厂函数抛出异常,调用者将无法收到部分构建对象的引用,因此将无法处理它。通过编写一个函数,该函数将正在构建的对象的引用作为“out”参数进行传递可能是更好的模式,因为即使构造失败,它也可以确保(可能是部分构建的)对象的处理,但是这种模式会使“using”块变得笨拙。 - supercat
显示剩余3条评论

6

这两者的区别在于,如果异常出现在

Con.Open()
'do whatever

Con.Dispose不会被调用。

我不熟悉VB语法,但在C#中,等效的代码将是

try
{
    con = new Connection();
    // Do whatever
}
finally
{
    if (con != null) con.Dispose();
}

3
你的代码出错了。当Connection构造函数抛出异常且“con”为空时,finalizer将会崩溃。将其移出try块。同时,using语句确保重新赋值变量不会引起问题。 - Hans Passant
@HansPassant:通常的模式是在try块内进行赋值,但在finally块中测试是否为null。如果赋值发生在try之前,那么在线程构造函数和赋值之间发生ThreadAbortException的风险无论赋值是在try内还是外都是一样的,但将其放在try内可以避免在赋值和try之间添加代码的可能性,这会增加对象被构造但未分配的危险。将构造函数... - supercat
@HansPassant:采用ref参数可能会进一步减轻这种风险,但这可能被认为是一种不寻常的模式(它将增加构造函数失败暴露不完整对象的可能性,但在某些情况下,暴露不完整对象并使其得到Dispose比对象消失而不得到Dispose更好。 - supercat
1
不,那不是正常的模式。而且也不是 using 语句的工作方式。看一下编译器使用 ildasm.exe 生成的代码,在构造函数调用之后注意 .try - Hans Passant
谢谢,我之前不知道ildasm。当我在using块中使用StreamWriter时,我自己检查了它。在.try之前,它调用[mscorlib]System.IO.StreamWriter并分配它,然后try开始。 - kayleeFrye_onDeck

3
使用 using 代码块可以确保在抛出异常时调用 Dispose() 方法。
而第二个示例没有这样做。
如果 Con.Open() 抛出异常,在第一个示例中,你可以确保会调用 Con.Dispose()。但在第二个示例中,异常将向上传递,Con.Dispose() 不会被调用。

假设我在Try/Catch块中处理第二个示例,并在Finally块中调用.Dispose(),它们有什么不同吗? - oscilatingcretin
如果你尝试着这样写代码 { Con.Open(); } finally { Con.Dispose(); },它将等同于 - jglouie
@oscilatingcretin 没有区别,但在编写/维护时使用更少的打字 :-) - Brian Warshaw
1
我也喜欢使用'using'来限定作用域。这样可以清楚地表明在该代码块之后不会再使用Con。 - jglouie

3
using语句确保在出现异常时对象会被处理。它相当于在finally块中调用dispose方法。

3

使用using将封装块置于try/finally中,在finally块中调用Dispose。这确保即使发生异常,也会调用Dispose。

出于安全原因,几乎所有情况下都应使用using


1
如果我没记错的话,使用 using 关键字可以确保对象被处理,无论包围它的代码块如何退出。它通过将该代码块包围在 try...finally 块中,并检查 used 变量是否为 null,然后在不为 null 的情况下对其进行处理来实现这一点。如果抛出异常,则允许其向上冒泡到堆栈。除此之外,它只是保证非空可处理对象的处理。
try
{
  var myDisposable = new DisposableObject();
  myDisposable.DoSomething();
}
finally
{
  if (myDisposable != null)
    ((IDisposable)myDisposable).Dispose();
}

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