为什么'使用'改善C#表现

时间:2010-06-17 14:32:59

标签: c# compiler-construction dispose idisposable using

在大多数情况下,C#编译器似乎可以自动调用Dispose()。像使用模式的大多数情况一样:

public void SomeMethod()
{
    ...

    using (var foo = new Foo())
    {
        ...
    }

    // Foo isn't use after here (obviously).
    ...
}

由于未使用foo(这是一个非常简单的检测),并且因为它不作为另一个方法的参数提供(这是一个适用于许多用例且可以扩展的假设),编译器可以< strong>自动且立即调用Dispose(),而开发人员无需执行此操作。

这意味着在大多数情况下,如果编译器做了一些聪明的工作,using就没用了。 IDisposable似乎低级别足以让我被编译器考虑在内。

现在为什么不这样做?这不会改善表现(如果开发人员...... )。

11 个答案:

答案 0 :(得分:21)

有几点:

调用Dispose不会提高性能。 IDisposable适用于使用运行时无法解决的有限和/或非托管资源的场景。

关于编译器如何处理代码中的IDisposable对象,没有明确而明显的机制。什么使它成为自动处置的候选者,什么不是?如果实例(或可能)暴露在方法之外?没有什么可说的,只是因为我将一个对象传递给另一个我希望它可以在方法范围之外使用的函数或类

例如,考虑一个工厂模式,它采用Stream并反序列化一个类的实例。

public class Foo
{
    public static Foo FromStream(System.IO.Stream stream) { ... }
}

我称之为:

Stream stream = new FileStream(path);

Foo foo = Foo.FromStream(stream);

现在,我可能会或可能不希望在方法退出时处理Stream。如果Foo的工厂从Stream读取所有必要的数据并且不再需要它,那么我希望它被处理掉。如果Foo对象必须保留流并在其生命周期内使用它,那么我不希望它被丢弃。

同样,从构造函数以外的其他东西检索的实例如Control.CreateGraphics()。这些实例可能存在于代码之外,因此编译器不会自动处理它们。

为用户提供控制(并提供类似using块的成语)可以明确用户的意图,并且可以更轻松地找到未正确处理IDisposable个实例的位置。如果编译器要自动处理某些实例,那么调试将变得更加困难,因为开发人员不得不破译自动处理规则如何应用于使用{{{}的每个代码块。 1}}对象。

最后,在一个类型上实现IDisposable有两个原因(按惯例)。

  1. 您正在使用非托管资源(意味着您正在进行P / Invoke调用,返回类似于必须由其他P / Invoke调用释放的句柄)
  2. 您的类型具有IDisposable的实例,当此对象的生命周期结束时应该将其处理掉。
  3. 在第一种情况下,所有这些类型都应该实现调用IDisposable的终结器并释放所有非托管资源,如果开发人员不这样做(这是为了防止内存和处理泄漏)。

答案 1 :(得分:16)

垃圾收集(虽然与IDisposable没有直接关系,清理未使用的对象)并不那么简单。

让我再说一点。自动调用Dispose()并不是那么简单。它也不会直接提高性能。稍后再详细说明。

如果您有以下代码:

public void DoSomeWork(SqlCommand command)
{
    SqlConnection conn = new SqlConnection(connString);

    conn.Open();

    command.Connection = conn;

    // Rest of the work here
 }

编译器如何知道您何时使用conn对象?或者,如果您传递了一些其他方法的引用?

明确调用Dispose()或使用using块明确说明您的意图并强制要求事情得到妥善清理。

现在,回到表演。简单地在对象上调用Dispose()并不能保证任何性能提升。当您完成对象时,Dispose()方法用于“清理”资源。

使用未管理的资源时可能会出现性能提升。如果托管对象未正确处理其未受管理的资源,则会发生内存泄漏。丑陋的东西。

决定将Dispose()调用到编译器会消除这种清晰度,并使调试内存泄漏由非托管资源造成更加困难。

答案 2 :(得分:3)

您要求编译器对您的代码执行语义分析。在源中的某个点之后未明确引用某些事实并不意味着它未被使用。如果我创建一个引用链并将一个引用传递给一个方法,该方法可能会或者可能不会将该引用存储在属性或其他持久容器中,我是否真的希望编译器能够遍历所有这些并找出我的真实内容

挥发性实体也可能是一个问题。

此外,using() {....}更具可读性和直观性,在可维护性方面非常值得。

作为工程师或程序员,我们力求高效,但这与 lazy 很少相同。

答案 3 :(得分:3)

查看MSDN Artilce for the C# Using Statement using语句只是一个捷径,可以避免尝试,最后在所有地方。调用dispose不是垃圾收集等低级功能。

正如您所见,使用被翻译成。

{
  Font font1 = new Font("Arial", 10.0f);
  try
  {
    byte charset = font1.GdiCharSet;
  }
  finally
  {
    if (font1 != null)
      ((IDisposable)font1).Dispose();
  }
}

编译器如何知道finally块的放置位置?它是否在垃圾收集上调用它?

一旦离开方法,Garabage Collection就不会发生。阅读垃圾收集中的article以更好地理解它。只有在没有对象的引用之后。资源的捆绑时间可能比需要的时间长得多。

不断涌现的想法是编译器不应该保护那些不清理资源的开发人员。仅仅因为管理一种语言并不意味着它会保护自己。

答案 4 :(得分:2)

C ++支持这个;他们称之为“参考类型的堆栈语义”。我支持将其添加到C#中,但它需要不同的语法(根据是否将局部变量传递给另一个方法来改变语义不是一个好主意。)

答案 5 :(得分:2)

我认为你正在考虑终结者。终结器在c#中使用析构函数语法,它们由垃圾收集器自动调用。终结器仅适用于清理非托管资源时使用。

Dispose旨在允许及早清理非托管资源(并且它也可用于清理托管资源)。

检测实际上比它看起来更棘手。如果你有这样的代码怎么办:


  var mydisposable = new...
  AMethod(mydisposable);
  // (not used again)

AMethod中的某些代码可能会保留对myDisposable的引用。

  • 可能会将其分配给该方法中的实例变量

  • 也许myDisposable订阅AMethod内的事件(然后事件发布者持有对myDisposable的引用)

  • 可能是另一个线程由AMethod

  • 生成
  • 也许mydisposable被AMethod内部的匿名方法或lamba表达式“封闭”。

所有这些都让人很难确定你的对象已经不再使用了,所以Dispose可以让开发人员说“好吧,我知道现在运行我的清理代码是安全的”; < / p>

请记住,dispose不会释放您的对象 - 只有GC才能这样做。 (GC确实能够理解我描述的所有场景,它知道何时清理对象,如果你确实需要在GC检测到没有引用时运行代码,你可以使用终结器)。但是要小心终结器 - 它们仅适用于您的类所拥有的非托管分配。

您可以在此处阅读有关此内容的更多信息: http://msdn.microsoft.com/en-us/magazine/bb985010.aspx 在这里:http://www.bluebytesoftware.com/blog/2005/04/08/DGUpdateDisposeFinalizationAndResourceManagement.aspx

如果您需要非托管句柄清理,请阅读SafeHandles。

答案 6 :(得分:1)

编译器不负责解释应用程序中的作用域,并在不再需要内存时做出类似的事情。事实上,我很确定这是一个不可能解决的问题,因为无论多么聪明,编译器都无法知道程序在运行时的样子。

这就是为什么我们有垃圾收集。垃圾收集的问题在于它在不确定的时间间隔上运行,并且通常在对象实现IDisposable时,原因是您希望能够立即处理它。就像,立即 。诸如数据库连接之类的构造不仅仅是一次性的,因为它们在被破坏时还有一些特殊的工作要做 - 这也是因为它们很少。

答案 7 :(得分:0)

对于G.C.我似乎很难。要知道以后在同一方法中不再使用此变量。显然,如果你离开方法,并且不再对你的变量进行进一步的引用,那么G.C.将处置它。但是在你的样本中使用using,告诉G.C.您 确定 ,您将不再使用此变量。

答案 8 :(得分:0)

using statement与性能无关(除非您考虑避免资源/内存泄漏作为性能)。

它可以保证在超出范围时对相关对象调用IDisposable.Dispose方法,即使在使用块内发生异常也是如此。

然后,Dispose()方法负责释放对象使用的任何资源。这些通常是非托管资源,如文件,字体,图像等,但也可以是托管对象上的简单“清理”活动(但不是垃圾收集)。

当然,如果Dispose()方法执行得很糟糕,则using语句提供零利益。

答案 9 :(得分:0)

我认为OP正在说“当编译器应该能够非常容易地完成它时,为什么要使用'''。”

我认为OP正在说

public void SomeMethod() 
{ 
    ... 

    var foo = new Foo();

    ... do stuff with Foo ... 


    // Foo isn't use after here (obviously). 
    ... 
} 

应该相当于

public void SomeMethod() 
{ 
    ... 

    using (var foo = new Foo())
    {
    ... do stuff with Foo ... 
    }

    // Foo isn't use after here (obviously). 
    ... 
} 

因为不再使用Foo。

答案当然是编译器不能很容易地解决它。垃圾收集(在.NET中神奇地称为“Dispose()”)是一个非常复杂的领域。仅仅因为下面未使用该符号并不意味着该变量未被使用。

举个例子:

public void SomeMethod() 
{ 
    ... 

    var foo = new Foo();
    foo.DoStuffWith(someRandomObject);
    someOtherClass.Method(foo);

    // Foo isn't use after here (obviously).
    // Or is it?? 
    ... 
} 

在这个例子中,someRandomObject和someOtherClass 可能都引用了Foo指出的内容,所以如果我们调用Foo.Dispose(),它会破坏它们。你说你只是想象一下这个简单的案例,但你提出的唯一“简单案例”就是你没有从Foo调用方法并且不将Foo或其任何成员传递给其他任何东西 - 有效的时候甚至根本不使用Foo,在这种情况下你可能没有必要声明它。即使这样,你也永远不能确定某种反射或事件hackery没有通过它的创建来引用Foo,或者Foo在它的构造函数中没有用其它东西来连接它。

答案 10 :(得分:0)

除了上面列出的优良原因之外,由于无法在所有情况下都能可靠地解决问题,因此这些“简单”案例是代码分析工具可以检测到的内容。让编译器确定性地完成任务,并让你的自动代码分析工具告诉你何时你正在做一些愚蠢的事情,比如忘记调用Dispose。

相关问题