如何在C#中执行C ++样式的析构函数?

时间:2020-03-05 18:48:44  来源:igfitidea点击:

我有一个通过IDisposable带有Dispose函数的Cclass。它打算在" using"块内使用,以便可以立即释放它处理的昂贵资源。

问题是当在调用Dispose之前引发异常而程序员忽略了使用using或者finally时,发生了一个错误。

在C ++中,我永远不必为此担心。对类的析构函数的调用将自动插入到对象作用域的末尾。避免发生这种情况的唯一方法是使用new运算符并将对象保留在指针后面,但是程序员不需要额外的工作,因为他们不是偶然地做的事情,例如忘记使用" using"。

有什么办法可以在C#中自动使用using块?

非常感谢。

更新:

我想解释为什么我不接受终结器答案。这些答案本身在技术上是正确的,但它们不是C ++样式的析构函数。

这是我发现的错误,已简化为基本要点...

try
{
    PleaseDisposeMe a = new PleaseDisposeMe();
    throw new Exception();
    a.Dispose();
}
catch (Exception ex)
{
    Log(ex);
}

// This next call will throw a time-out exception unless the GC
// runs a.Dispose in time.
PleaseDisposeMe b = new PleaseDisposeMe();

使用FXCop是一个很好的建议,但是如果这是我唯一的答案,那么我的问题就必须成为Cpeople的恳求,或者使用C ++。二十嵌套使用语句的人吗?

解决方案

回答

~ClassName()
{
}

编辑(粗体):

如果将对象移出范围并由垃圾收集器整理,则将调用if,但是这不是确定性的,不能保证在任何特定时间发生。

这称为终结器。所有带有终结器的对象都会由垃圾收集器放置在特殊的终结队列中,在该队列上将调用finalize方法(因此,从技术上讲,声明空终结器会降低性能)。

public class DisposableFinalisableClass : IDisposable
    {
        ~DisposableFinalisableClass()
        {
            Dispose(false);
        }

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

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // tidy managed resources
            }

            // tidy unmanaged resources
        }
    }

对于框架资源,根据框架准则的"接受"处置方式如下:

回答

因此,上面的意思是,如果有人调用Dispose,则对非托管资源进行整理。但是,如果有人忘记调用Dispose或者阻止Dispose被称为非托管资源的异常,仍然会被整理掉,直到GC收到肮脏的手套(包括应用程序关闭或者意外结束)后才进行整理。 )。

不幸的是,没有任何方法可以直接在代码中直接执行此操作。如果这是内部问题,则可以使用各种代码分析解决方案来解决此类问题。我们是否研究过FxCop?我认为这将解决这些情况,并且在所有IDisposable对象可能悬而未决的情况下都可以使用。如果它是人们在组织外部使用的组件,并且我们不需要FxCop,那么文档实际上是我们唯一的手段:)。

回答

编辑:对于终结器,这并不能真正保证终结器何时发生。因此,这可能是解决方案,但要视情况而定。

这与程序员忘记在C ++中使用delete没什么不同,除了至少在这里,垃圾收集器最终仍会赶上它。

回答

如果我们担心的唯一资源是内存,则无需使用IDisposable。该框架将自行处理。 IDisposable仅适用于非托管资源,例如数据库连接,文件流,套接字等。

If will get called when the object is moved out of scope and is tidied by the garbage collector.

@争吵

该语句具有误导性,而且我读错了它:绝对不能保证何时调用终结器。我们完全正确,billpg应该实现终结器;但是,当对象超出他想要的范围时,将不会自动调用它。证据,"完成操作"下的第一个项目符号有以下限制。

回答

实际上,Microsoft授予Chris Sells以创建.NET的实现,该实现使用引用计数而不是垃圾回收Link。事实证明,这对性能造成了很大的影响。

更好的设计是使此类在释放之前自行释放昂贵的资源。

回答

例如,如果它是数据库连接,则仅在需要时连接并在释放实际类很长时间之前立即释放。

  • 每个IDisposable类必须具有终结器
  • 每当使用IDisposable对象时,都必须在" using"块内使用它。唯一的例外是,如果对象是另一个类的成员,则包含类必须是IDisposable的,并且必须在其自己的" Dispose"实现中调用该成员的" Dispose"方法。这意味着除了在另一个" Dispose"方法内部,开发人员绝不应调用" Dispose",从而消除了问题中描述的错误。
  • 每个终结器中的代码必须以警告/错误日志开头,通知我们已调用终结器。这样,在发布代码之前,我们极有机会发现上述错误,此外,这还可能暗示系统中发生了错误。

在我工作的地方,我们使用以下准则:

为了使我们的生活更轻松,我们在基础架构中还有一个SafeDispose方法,该方法在try-catch块(带有错误日志记录)中调用其参数的Dispose方法,以防万一(尽管Dispose方法不应该引发异常) )。

另请参阅:克里斯·里昂(Chris Lyon)关于IDisposable的建议

编辑:
@Quarrelsome:我们应该做的一件事是在'Dispose'中调用GC.SuppressFinalize,这样,如果对象被处置,则不会被"重新处置"。

class MyDisposable: IDisposable {
    public void Dispose() {
        lock(this) {
            if (disposed) {
                return;
            }

            disposed = true;
        }

        GC.SuppressFinalize(this);

        // Do actual disposing here ...
    }

    private bool disposed = false;
}

通常也建议持有一个标志,该标志指示对象是否已经被处置。跟随模式通常非常好:

回答

当然,锁定并非总是必要的,但是如果不确定是否在多线程环境中使用类,建议保留它。

最佳实践是在类中使用终结器,并始终使用" using"块。

虽然并没有直接的等效项,但终结器看起来像C析构函数,但行为有所不同。

using (SqlConnection con = new SqlConnection("DB con str") )
using (SqlCommand com = new SqlCommand( con, "sql query") )
{
    //now code is indented one level
    //technically we're nested twice
}

我们应该嵌套" using"块,这就是为什么Ccode布局默认将它们放在同一行上的原因...

PleaseDisposeMe a;
try
{
    a = new PleaseDisposeMe();
    throw new Exception();
}
catch (Exception ex) { Log(ex); }  
finally {    
    //this always executes, even with the exception
    a.Dispose(); 
}

当我们不使用using时,我们仍然可以在幕后做它所要做的事情:

段落数量不匹配