【问题标题】:Is using try/finally a good practice for memory management?使用 try/finally 是内存管理的好习惯吗?
【发布时间】:2016-12-21 08:11:44
【问题描述】:

我的一位前辈告诉我使用try/ finally 块来清除初始化对象数据的所有方法。 例如:

var serviceProxy = new NotificationServiceProxy();
try
{
     return serviceProxy.GetNotifications(userID, request.Filters, request.FilterProperty, usertypeid);
}
finally
{
     serviceProxy = null;
}

这是一个好习惯吗?如果我在所有方法中使用try/ catch 来清除已初始化的对象数据。

【问题讨论】:

  • 没有必要像这样清除变量。一旦它们离开作用域,它们就会被垃圾回收(如果可能的话,甚至更早)。
  • 这取决于您要清除的变量类型。值类型 - 不需要。参考类型 - 是的,你可以
  • @SouvikGhosh:这是不正确的。如果您将引用类型的变量设置为null,您只会取消引用它。它仍然会留在堆上,直到它被收集。如果您不将其设置为 null,它将在方法退出时取消引用(并且仍保留在堆上)。
  • @Sefe 我的意思是一些 COM 对象或网络对象。我们不是优雅地释放它们而不是依赖 GC 吗?
  • @SouvikGhosh:如果需要释放对象,则需要处置它们。取消引用对释放任何内容没有任何作用。它只是取消引用,不多也不少。

标签: c# garbage-collection try-finally


【解决方案1】:

不在 C# 中,请改用 using

using(var serviceProxy = new NotificationServiceProxy()){
    serviceProxy.GetNotifications(userID, request.Filters, request.FilterProperty, usertypeid);
    // Do stuff with the service proxy
}

// serviceProxy has now been cleaned up

这是确保IDisposable 变量得到清理的模式。

对于非一次性的实例,这不是必需的 - 只需让它们超出范围并将其留给 GC。

如果NotificationServiceProxy 使用大量资源,并且您需要确保它已正确完成,则将其设为一次性并始终将其包装在using 或另一个也实现IDisposable 的类中。如果没有,那么这个try-finally 模式就是浪费时间。

【讨论】:

  • 我猜使用 block 只能用于实现 IDisposable 接口的对象。
  • @SouvikGhosh 是的。实例是 IDisposable 并且应该包含在 using 中,或者不是并且 try-finally 什么都不做。
  • 只继承IDisposable接口到NotificationServiceProxy类就够了吗?
  • @King_Fisher 如果您只处理 .NET IDisposable 对象,那么可以。如果您有非托管资源(例如 COM+ 组件),那么您还需要一个 ~Finaliser - Visual Studio 会自动为您创建方法存根。更多msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx
  • 上面写着 'NotificationServiceProxy' does not implement interface member 'IDisposable.Dispose()' 所以我添加了 public void Dispose() { GC.SuppressFinalize(this); } ,我是否必须在 Dispose 方法中添加片段代码?
【解决方案2】:

不,这不是一个好习惯。

  • 您正在手动工作,编译器/垃圾收集器将为您完成。这是您需要在以后删除或维护的不必要的时间和不必要的代码混乱。

  • 您完全错过了.Dispose() 服务参考。虽然这可以finally 块中完成,但using 块正是为此目的而存在的。

【讨论】:

    【解决方案3】:

    您不需要清除局部变量。在退出该方法之前,无论如何都会恢复堆栈(这将释放变量使用的堆栈空间)并将局部变量设置为null不会释放堆空间(垃圾收集器会这样做那)。如果您需要进行一些清理,例如对象处置或关闭文件,请使用 try finally

    【讨论】:

      【解决方案4】:

      这是否是一个好习惯,这取决于场景。捕获异常通常很好,这样您就可以优雅地处理意外情况。如果您的程序已经开发了一些其他方法来处理异常情况,那么 Try/Finally 块应该就足够了,因为 Catch 块有一些性能损失。

      请查看来自 MSDN 的 Consideration When Using Try Catch 文章。

      【讨论】:

        【解决方案5】:

        首先,您无需清除局部变量(Garbage Collector 会为您完成),但是,它是安全的:

         Object o = new Object(); 
         ...
         o = null; // safe, but not required
        

        你要做的就是清除非托管资源;典型的方法是实现IDisposable 接口并将相应的实例包装到using

         // Providing that serviceProxy implements IDisposable
         using (var serviceProxy = new NotificationServiceProxy()) {
           return serviceProxy.GetNotifications(
             userID, 
             request.Filters, 
             request.FilterProperty, 
             usertypeid);
         } // <- here .Net will call serviceProxy.Dispose() and free the resources allocated
        

        有时您必须恢复初始状态,这就是try..finally 的设计目的:

          Cursor savedCursor = Cursor.Current;
        
          try {
            Cursor.Current = Cursors.WaitCursor;
            ... 
          }
          finally {
            // Rain (exception thrown) or shine (no exception), please, restore my cursor
            Cursor.Current = savedCursor;
          }
        

        在您的特定情况下,我看不到任何要恢复的状态,这就是为什么 try..finally 不是应该使用的模式。但是,serviceProxy 很可能分配了一些 非托管资源(例如,TCP/IP 端口、连接或类似的),因此您似乎应该为 NotificationServiceProxy 类实现 IDisposable(如果它是尚未实现)并将实例包装到using

        【讨论】:

          【解决方案6】:

          当您需要 HAS 发生某些事情(例如无论您的操作是成功还是失败)都处置 IDisposable 对象时,这是有道理的。

          try
          {
              myDisposable.Do();
          }
          finally
          {
              myDisposable.Dispose(); 
          }
          

          你有一个内置机制可以做到这一点:使用

          using(IDisposable myDisposable = new Something())
          {
              myDisposable.Do();
          }
          

          现在有好的做法:

          这取决于您的错误处理策略和清理需求:

          • 如果我有一些事情必须完成,无论操作成功还是失败,我都会尝试 finally 块。

          • 如果我有一些只需要错误处理而不需要清理的东西,我会在没有 finally 块的情况下放置 try catch

          • 如果我必须进行某种错误处理,我也会在其中放置一个 catch 块

          • 如果我不想像日志那样在那里进行一些错误处理,然后让异常从该堆栈传播,我也会重新抛出异常。

          或上述条件的任何排列..

          例如:

           try
           {
               myDisposable.Do();
           }
           catch(Exception e)
           {
               Log("Error at something something",e); 
               throw e;
           }
           finally
           {
               myDisposable.Dispose(); 
           }
          

          我并没有在每个地方都放置 try catch finally,我希望我的代码中确实包含它们的位置能够突出显示这些是已知由于某些已知原因而出错的操作。

          我会欢迎我尚未意识到的新异常并相应地处理它们。

          【讨论】:

            【解决方案7】:

            是的,如果您需要处理变量。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-03-30
              • 2013-04-14
              • 2012-08-22
              • 1970-01-01
              • 2014-07-24
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多