如何确定使用'using'的最佳位置?

7

我对C#还比较陌生,更习惯于脚本语言。但我喜欢“using”的思想:你实例化一个对象,然后在需要它的范围内操作,完成后就让它自动清理。

然而,这对我来说并不是那么自然。当别人用它给我展示一些例子时,我认为这是一个好工具,但在自己的编程中从未考虑过使用它来解决问题。

我应该如何识别使用using的好地方,以及如何与try-catch块结合使用?它们是否应该放在块内部,或者通常需要将using语句包含在try块内?

11个回答

18
using只能用于实现了IDisposable接口的类型;它保证即使发生错误,也会调用Dispose()方法。

以下代码:

using (MyDisposableType x = new MyDisposableType())
{  
    // use x
}

等同于这个:

MyDisposableType x = new MyDisposableType();
try
{  
    // use x
}
finally 
{ 
    x.Dispose(); 
}

那么...在任何实现IDisposable接口的对象上都要使用它吗?总是这样吗? - danieltalsky
请注意,您永远不应将事务逻辑放在 finally 中,因为无法绝对保证其运行。 至少在我们获得事务性内存之前是这样的。例如,如果您拔掉电源插头,则 finally 不会运行! - Jonathan Parker
那么你会怎么做呢?防止代码在关机时执行某些操作 :D - RvdK
如果在关机时不处理资源,会很遗憾地泄漏它们 :) - dr. evil
1
@Jonathan Parker - 没有比 'finally' 更好的地方来放置事务逻辑。 - Daniel Earwicker
显示剩余2条评论

9
我很少编写try/catch块 - 大多数异常都会被抛到(接近)堆栈的顶部。如果我确实需要一个try/catch块,我不确定我在将其放置在using语句内部还是外部方面是否特别一致。这主要取决于您是否希望在运行异常处理代码之前或之后处理资源的释放。
如果您想知道何时应编写using语句 - 任何时候当您“拥有”实现IDisposable接口(直接或间接通过继承)并控制其生命周期的对象时。通常是使用非托管资源(如文件句柄或网络连接)的对象。这并不总是非常明显,但您可以通过经验学习。几乎与IO有关的所有内容都是可处置的,Windows句柄(例如字体等)也是类似的。

为了澄清实现IDisposable接口的对象,无论是直接实现还是通过继承(因为danieltalsky“对C#有些新”)。 - Jonathan Parker
好的,“对于 C# 来说还比较新”指的是“在 C# 中编写商业代码不到 8 个月”。例如,我已经熟悉了“using”与“IDisposable”的连接,这也在我的问题中提到了。而我缺失的是“生命周期控制”的部分。 - danieltalsky

3

我曾经认为它的意思是“每当使用实现IDisposable接口的类型时,如果你不再需要这个特定的实例,就使用它”。


2
如果您想知道如何在自己的设计中创造性地使用它,请查看您自己的代码,找出绝对必须在封闭块退出之前执行特定代码块的情况。这些是 try/finallyusing 可以帮助您的情况。
特别是,如果您已经尝试通过捕获所有异常来实现此目标,则确实必须改为使用 try/finallyusing
如果该模式发生多次,您可以创建一个实现 IDisposable 的类来捕获该模式,并使用 using 语句调用该模式。但如果您有一个特定的只出现一次的情况,那么就只需使用 try/finally
两者非常相似 - 事实上,using 是基于 try/finally 指定的,但即使我们只有 using,也可以自己构建 try/finally
public class DisposeAnything : IDisposable
{
    public Action Disposer;

    public void Dispose()
    {
        Disposer();
    }
}

现在你可以说:

using (new DisposeAnything 
      {
          Disposer = () => File.Delete(tempFileName) 
      })
{
    // blah
}

这与以下内容相同:

try
{
    // blah
}
finally
{
    File.Delete(tempFileName);
}

可以将其视为在离开作用域时执行一些代码的方法。


嘿,将处理逻辑参数化是一个有趣的想法。我想象有很多方法可以做到这一点。酷。 - Charlie Flowers
把它推向极端,这样做没有太多意义!但是确实存在许多中间情况。您可能会发现这个讨论很有趣:http://incrediblejourneysintotheknown.blogspot.com/2009/02/functional-replacement-for-using.html - Daniel Earwicker

1

当您需要确定性对象处理时,请使用using。例如,如果您打开一个文件,则该文件将被锁定。您通常希望尽快关闭文件,以便其他程序可以访问它。如果您不使用using并编写类似以下内容的代码:

System.IO.FileStream writeStream = new System.IO.FileStream( fileName, System.IO.FileMode.OpenOrCreate ) );
System.IO.BinaryWriter writer = new System.IO.BinaryWriter( writeStream ) );
//do smth 

当“做某事”期间发生异常时,您不知道在何时对象操作文件被实际处置并关闭文件。使用 using,您可以确定一旦离开 using 语句块 - 直接或通过异常 - 在 using 语句中的对象将通过调用 IDisposable::Dispose: 进行处置:

using( System.IO.FileStream writeStream = new System.IO.FileStream( fileName, System.IO.FileMode.OpenOrCreate ) ) {
    using( System.IO.BinaryWriter writer = new System.IO.BinaryWriter( writeStream ) ) {
        //do stmth
    }
}

不,这个对象并没有被终结。它只是被处理了。终结和处理是非常不同的。通常,处理会抑制终结器(如果有的话),但它们并不是同一件事情。 - Jon Skeet
当您需要确定性对象处理时,请使用using。如果一个对象实现了IDisposable接口,那么在不再需要该对象时,应该始终将其处理掉。通过实现IDisposable接口,对象表明“我(可能)使用需要被处理的资源”。 - pero
如果所涉及的对象的实现发生变化,立即处理变得至关重要怎么办?在使用后不进行处理的好处是什么? - pero

1

你可以在 try/catch 块内部包含 using,也可以在 using 内部包含 try/catch 块。

使用 using 的一个好处是在使用 DBConnection 和 DBCommands 进行数据库操作时:

using (SqlConnection conn = new SqlConnection(connectionString))
using (SqlCommand command = new SqlCommand(sql, conn))
{
  // do something here
}

现在当您离开使用块时,您的命令将被处理并且连接将关闭。

1

米奇说得没错。使用 using 的主要目的是确保 IDisposable 对象被处理,而不必编写 try/catch 或 try/finally 语句。

现在,还有一种更高级的用法可能也很有趣。当您使用 using 语句时,编译器会生成一个 try/finally,它还会在生成的 finally 中为您生成一个调用 Dispose() 的调用。您可以将此 Dispose() 方法用作“钩子”来执行任何您想要的操作... 不一定与释放资源有关。

例如,Jeffrey Richter 在他编写的计时器对象中使用了这个方法。您可以像这样使用它(仅概念性):

using(var x = new Timer())
{
  // Do the operation you want timed
}

// Now, the timer has been stopped, because the 
// compiler-generated call to Dispose() has already 
// occurred, and Jeffrey uses Dispose() to stop 
// the timer.

看起来有点棘手。在闭合括号之后,您无法访问x。那么,您如何利用已停止的计时器呢? - pero
是的,这是真的。在 Richter 的计时器中,他将经过的时间(以及发生的垃圾回收次数)输出到控制台。因此,在闭合括号之后,他没有其他需要引用该对象的地方。 - Charlie Flowers
然而,如果你想要的话,也有办法实现这一点。可以让对象触发事件或将结果放入集合中。或者您可以使用lambda操作参数化对象以在其内部执行。但是这已经远离了最初的问题。 - Charlie Flowers

1

除了Mitch说的之外...

您可以在try..catch块内部或外部使用using语句,这取决于您想要实现的目标,例如是否合理地期望某些内容在使用特定对象时引发异常并计划从中恢复。

同样,如果需要,您也可以在finally块中处理实现IDisposable接口的对象的Dispose方法。


1
如果一个类实现了IDisposable,那么很可能有充分的理由。因此,任何实现IDisposable的类都应该被处理掉。

1

鉴于它被称为“语法糖”,并且将生成与try/finally dispose结构相同的IL,因此它实际上只是一种优雅的方式来“简写”这样的代码。

我喜欢在使用可处理对象的代码段(例如访问文件和图形对象等资源)时使用它来简化代码,并确保我不会忘记处理资源对象的释放。


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