如何在.net中布置一个类?

时间:2020-03-05 18:39:53  来源:igfitidea点击:

.NET垃圾收集器最终将释放内存,但是如果我们要立即收回该内存怎么办?我们需要在类MyClass中使用什么代码来调用

MyClass.Dispose()

并释放MyClass中变量和对象的所有已用空间?

解决方案

回答

本文有一个非常简单的演练。但是,必须调用GC而不是自然而然地使用它通常是不良的设计/内存管理的信号,尤其是在没有消耗有限的资源(连接,句柄以及其他通常导致实现IDisposable的事物)的情况下。

是什么导致我们需要执行此操作?

回答

public class MyClass : IDisposable
{
    public void Dispose()
    {
       // cleanup here
    }
}

那么你可以做这样的事情

MyClass todispose = new MyClass();
todispose.Dispose(); // instance is disposed right here

或者

using (MyClass instance = new MyClass())
{

}
// instance will be disposed right here as it goes out of scope

回答

如果MyClass实现IDisposable,则可以做到这一点。

MyClass.Dispose();

Cis最佳实践:

using( MyClass x = new MyClass() ) {
    //do stuff
}

这样一来,就可以将尝试处理完毕,并确保不会遗漏任何内容。

回答

如果我们不想(或者不能)在类上实现IDisposable,则可以像这样强制垃圾收集(但是速度很慢)-

GC.Collect();

回答

IDisposable与释放内存无关。 IDisposable是释放非托管资源的一种模式-内存绝对是托管资源。

指向GC.Collect()的链接是正确的答案,尽管Microsoft .NET文档通常不鼓励使用此功能。

编辑:我已经为这个答案赢得了大量业力,我对此有一定的责任,以免对.NET资源管理的新来者产生错误的印象。

在.NET进程内部,有两种资源管理的资源和非资源管理的资源。 "托管"表示运行时由资源控制,而"非托管"表示这是程序员的责任。如今,我们在.NET中实际上只关心一种托管资源-内存。程序员告诉运行时分配内存,然后由运行时确定何时可以释放内存。 .NET为此目的使用的机制称为垃圾收集,我们可以简单地使用Google在Internet上找到大量有关GC的信息。

对于其他类型的资源,.NET对清理它们一无所知,因此它必须依靠程序员来做正确的事情。为此,该平台为程序员提供了三种工具:

  • VB和C#中的IDisposable接口和" using"语句
  • 终结者
  • 由许多BCL类实现的IDisposable模式

首先,程序员可以有效地获取资源,使用资源,然后在同一方法中释放所有资源。

using (DisposableObject tmp = DisposableObject.AcquireResource()) {
    // Do something with tmp
}
// At this point, tmp.Dispose() will automatically have been called
// BUT, tmp may still a perfectly valid object that still takes up memory

如果" AcquireResource"是(例如)打开文件的工厂方法,而" Dispose"自动关闭文件,则此代码无法泄漏文件资源。但是" tmp"对象本身的内存可能仍会分配。这是因为IDisposable接口与垃圾回收器完全没有连接。如果确实要确保释放内存,则唯一的选择是调用GC.Collect()以强制进行垃圾回收。

但是,不能足够强调这可能不是一个好主意。通常最好让垃圾收集器执行其设计要执行的操作,即管理内存。

如果该资源使用了较长的时间,从而使它的寿命跨越了几种方法,会发生什么?显然," using"语句不再适用,因此程序员在处理完资源后将不得不手动调用" Dispose"。如果程序员忘记了会怎样?如果没有后备,则无论资源未被正确释放,该进程或者计算机最终都将耗尽。

这就是终结器的用处。终结器是类上的一种与垃圾回收器有特殊关系的方法。 GC承诺-在为该类型的任何对象释放内存之前-它将首先为终结器提供进行某种清理的机会。

因此,就文件而言,理论上我们根本不需要手动关闭文件。我们可以等到垃圾收集器到达它,然后让终结器完成工作。不幸的是,这在实践中效果不佳,因为垃圾收集器无法确定地运行。该文件可能保持打开状态的时间比程序员预期的时间长得多。并且如果保持打开状态有足够的文件,则在尝试打开其他文件时系统可能会失败。

对于大多数资源,我们都希望这两件事。我们希望约定能够说"我们现在已经完成了此资源",并且我们希望确保如果我们忘记手动进行清理,至少有一定机会自动进行清理。这就是" IDisposable"模式起作用的地方。这是一个约定,允许IDispose和终结器一起很好地玩耍。通过查看IDisposable的官方文档,可以了解模式的工作原理。

底线:如果我们真正想做的只是确保释放内存,那么IDisposable和finalizer将无法为我们提供帮助。但是IDisposable接口是所有.NET程序员都应该理解的极其重要模式的一部分。

回答

还应该提到处理并不总是指内存吗?与内存相比,我更经常地将资源(例如对文件的引用)配置为文件。 GC.Collect()与CLR垃圾收集器直接相关,并且可能(也可能不会)释放内存(在"任务管理器"中)。这可能会对应用程序产生负面影响(例如性能)。

归根结底,我们为什么要立即恢复内存?如果其他地方存在内存压力,在大多数情况下,操作系统将为我们提供内存。

回答

尽管有多种方法可以强制运行GC,但我们并不能真正强迫它清理对象,但没有任何内容表明它可以清理我们想要/期望的所有对象。最好以尝试捕获的方式调用处置,最后以最终处置(VB.NET rulz)的方式进行处置。但是Dispose用于清理对象以确定性方式分配的系统资源(内存,句柄,数据库连接等),Dispose不会(也不能)清理对象本身使用的内存,仅清理GC可以做到的。

回答

我们只能处置实现IDisposable接口的实例。

要强制垃圾回收立即释放(非托管的)内存,请执行以下操作:

GC.Collect();  
GC.WaitForPendingFinalizers();

这通常是不好的做法,但是例如,.NET框架的x64版本中存在一个错误,该错误使GC在某些情况下的行为奇怪,然后我们可能想要执行此操作。我不知道该错误是否已经解决。有人知道吗?

要处置课程,请执行以下操作:

instance.Dispose();

或者像这样:

using(MyClass instance = new MyClass())
{
    // Your cool code.
}

在编译时将转换为:

MyClass instance = null;    

try
{
    instance = new MyClass();        
    // Your cool code.
}
finally
{
    if(instance != null)
        instance.Dispose();
}

我们可以像这样实现IDisposable接口:

public class MyClass : IDisposable
{
    private bool disposed;

    /// <summary>
    /// Construction
    /// </summary>
    public MyClass()
    {
    }

    /// <summary>
    /// Destructor
    /// </summary>
    ~MyClass()
    {
        this.Dispose(false);
    }

    /// <summary>
    /// The dispose method that implements IDisposable.
    /// </summary>
    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// The virtual dispose method that allows
    /// classes inherithed from this one to dispose their resources.
    /// </summary>
    /// <param name="disposing"></param>
    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources here.
            }

            // Dispose unmanaged resources here.
        }

        disposed = true;
    }
}

回答

IDisposable接口实际上适用于包含非托管资源的类。如果类不包含非托管资源,那么为什么我们需要在垃圾收集器释放它之前释放资源?否则,只需确保对象尽可能迟地实例化并尽快超出范围。

回答

看看这篇文章

当回收内存时,实现Dispose模式,IDisposable和/或者终结器绝对没有关系。相反,它与告诉GC如何回收该内存有关。调用Dispose()时,我们绝不会与GC进行交互。

GC仅在确定需要时才运行(称为内存压力),然后(并且只有那时)它将为未使用的对象释放内存并压缩内存空间。

我们可以调用GC.Collect(),但除非有充分的理由(几乎总是"从不"),否则我们不应该这样做。当我们像这样强制执行带外收集周期时,实际上会导致GC做更多的工作,最终最终会损害应用程序性能。在GC收集周期的整个过程中,应用程序实际上处于冻结状态……运行的GC周期越多,应用程序花费在冻结上的时间就越长。

我们还可以进行一些本机Win32 API调用以释放工作集,但是即使没有充分的理由,也应避免使用这些调用。

垃圾收集的运行时背后的整个前提是,我们不必担心(尽可能多地)运行时何时分配/取消分配实际内存。我们只需要担心要确保对象在被询问时知道如何自行清理。

回答

我们可以在C ++中进行确定性的对象销毁

我们永远都不想调用GC.Collect,它会与垃圾收集器的自整定混乱,以检测内存压力,并且在某些情况下,除了增加堆中每个对象的当前生成量外,什么也没有做。

对于那些发布IDisposable答案。调用Dispose方法不会破坏对象所描述的对象。

回答

抱歉,此处选择的答案不正确。正如随后有人指出的那样,Dispose和实现IDisposable与释放与.NET类关联的内存无关。传统上,它主要用于释放非托管资源,例如文件句柄等。

尽管应用程序可以调用GC.Collect()尝试通过垃圾收集器强制进行收集,但这只会真正影响那些在可到达队列中处于正确生成级别的项目。因此,如果我们清除了对该对象的所有引用,则可能在释放实际内存之前仍可能是对GC.Collect()的几次调用。

我们没有在问题中说为什么要立即释放内存。我知道有时可能会出现异常情况,但是严重的是,在托管代码中,几乎总是最好让运行时处理内存管理。

如果我们认为代码消耗的内存比GC释放内存的速度更快,则可能是最好的建议,那么我们应该查看代码以确保在静态成员等中存在的任何数据结构中都不再引用不再需要的对象。还应尝试避免使用圆形对象引用的情况,因为它们可能也不会被释放。

回答

@Curt Hagenlocher又回到了前面。我不知道为什么有这么多人在错误的地方投了赞成票。

IDisposable用于托管资源。

终结器用于不受管理的资源。

只要我们只使用托管资源,@ Jon Limjap和我自己都是完全正确的。

对于使用非托管资源的类(请记住,绝大多数.Net类都不使用),Patrik的答案是全面的最佳实践。

避免使用GC.Collect是一种处理托管资源的慢速方法,除非我们已正确构建〜Finalizer,否则它不会对非托管资源执行任何操作。

我已根据https://stackoverflow.com/questions/14593/e​​tiquette-for-modifying-posts从原始问题中删除了主持人评论

回答

@基思:

IDisposable is for managed resources.
  
  Finalisers are for unmanaged resources.

抱歉,那是错误的。通常,终结器什么也不做。但是,如果处理模式已正确实现,则终结器将尝试调用"处理"。

Dispose有两个工作:

  • 释放不受管的资源,以及
  • 免费嵌套托管资源。

在这里,语句起作用了,因为确实如此,在完成操作时,对象永远不要尝试释放嵌套的托管资源,因为这些资源可能已经被释放了。但是,它仍然必须释放非托管资源。

不过,终结器除了调用`Dispose'并告诉它不要接触托管对象外没有其他工作。当手动(或者通过使用)调用Dispose时,应释放所有非托管资源,并将Dispose消息传递给嵌套对象(和基类方法),但这将永远不会释放任何(托管)内存。

回答

Konrad Rudolph是的,终结者通常什么都不做。除非我们要处理非托管资源,否则不应实施它。

然后,当我们实施它时,我们将使用Microsoft的处置模式(如前所述)

  • 公共Dispose()调用受保护的Dispose(true)-处理托管和非托管资源。调用Dispose()应该抑制最终确定。
  • 〜Finalize调用protected Dispose(false)`-仅处理非托管资源。如果我们无法调用public Dispose(),这可以防止非托管内存泄漏。

〜Finalize`很慢,除非我们有非托管资源要处理,否则不要使用它。

托管资源不会内存泄漏,它们只能浪费当前应用程序的资源并减慢其垃圾回收速度。非托管资源可能会泄漏,而〜Finalize`是确保它们不会泄漏的最佳实践。

无论哪种情况,"使用"都是最佳做法。

回答

Joe Duffy对"处置,完成和资源管理"的完整解释:

Earlier in the .NET Framework’s
  lifetime, finalizers were consistently
  referred to as destructors by C#
  programmers. As we become smarter over
  time, we are trying to come to terms
  with the fact that the Dispose method
  is really more equivalent to a C++
  destructor (deterministic), while the
  finalizer is something entirely
  separate (nondeterministic). The fact
  that C# borrowed the C++ destructor
  syntax (i.e. ~T()) surely had at least
  a little to do with the development of
  this misnomer.

回答

对这个问题的回答已经有些混乱了。

标题询问要处理的内容,但随后又说他们希望立即将内存恢复原状。

.Net是托管的,这意味着当我们编写.Net应用程序时,我们无需直接担心内存,而代价是我们也无法直接控制内存。

.Net决定什么时候最好清除和释放内存,而不是我们是.Net编码人员。

Dispose是一种告诉.Net我们已经完成某些事情的方法,但是实际上只有在最佳时机才释放内存。

基本上,.Net实际上会在最容易做到的时候将内存回收回来,因此在决定时间方面非常好。除非我们要编写占用大量内存的内容,否则通常不需要覆盖它(这是游戏通常不使用.Net编写但需要完全控制的原​​因的一部分)

在.Net中,我们可以使用GC.Collect()强制其立即执行,但这几乎总是不好的做法。如果.Net尚未清除它,那么这不是一个特别好的时间。

GC.Collect()拾取.Net标识为完成的对象。如果我们尚未处置需要的对象,.Net可能会决定保留该对象。这意味着GC.Collect()仅在正确实现一次性实例时才有效。

GC.Collect()不能代替IDisposable正确使用。

因此,Dispose和内存并不直接相关,但不必如此。正确处理将使.Net应用程序更高效,因此使用更少的内存。

在.Net中99%的时间中,以下是最佳实践:

规则1:如果我们不处理任何不受管理的东西或者实现" IDisposable"的东西,那么不必担心Dispose。

规则2:如果我们有一个实现IDisposable的局部变量,请确保在当前范围内摆脱它:

//using is best practice
using( SqlConnection con = new SqlConnection("my con str" ) )
{
    //do stuff
} 

//this is what 'using' actually compiles to:
SqlConnection con = new SqlConnection("my con str" ) ;
try
{
    //do stuff
}
finally
{
    con.Dispose();
}

规则3:如果类具有实现IDisposable的属性或者成员变量,则该类也应实现IDisposable。在该类的Dispose方法中,我们还可以处置IDisposable属性:

//rather basic example
public sealed MyClass :
   IDisposable
{   
    //this connection is disposable
    public SqlConnection MyConnection { get; set; }

    //make sure this gets rid of it too
    public Dispose() 
    {
        //if we still have a connection dispose it
        if( MyConnection != null )
            MyConnection.Dispose();

        //note that the connection might have already been disposed
        //always write disposals so that they can be called again
    }
}

这还不是很完整,这就是示例被密封的原因。继承类可能需要遵守下一条规则...

规则4:如果一个类使用非托管资源,则实现IDispose并添加一个终结器。

.Net无法使用非托管资源执行任何操作,因此现在我们要讨论内存。如果不清理,可能会导致内存泄漏。

Dispose方法需要同时处理托管资源和非托管资源。

终结器是一个安全陷阱,它确保如果有人创建了类的实例,并且无法对其进行处置,则.Net仍可以清除"危险"非托管资源。

~MyClass()
{
    //calls a protected method 
    //the false tells this method
    //not to bother with managed
    //resources
    this.Dispose(false);
}

public void Dispose()
{
    //calls the same method
    //passed true to tell it to
    //clean up managed and unmanaged 
    this.Dispose(true);

    //as dispose has been correctly
    //called we don't need the 

    //'backup' finaliser
    GC.SuppressFinalize(this);
}

最后,此Dispose的重载带有一个布尔标志:

protected virtual void Dispose(bool disposing)
{
    //check this hasn't been called already
    //remember that Dispose can be called again
    if (!disposed)
    {
        //this is passed true in the regular Dispose
        if (disposing)
        {
            // Dispose managed resources here.
        }

        //both regular Dispose and the finaliser
        //will hit this code
        // Dispose unmanaged resources here.
    }

    disposed = true;
}

请注意,一旦一切就绪,其他托管代码创建类的实例就可以像对待其他IDisposable一样对待它(规则2和3)。

回答

@基思

我同意我们除#4之外的所有规则。仅在非常特殊的情况下才添加终结器。如果类使用非托管资源,则应在Dispose(bool)函数中清理那些资源。当bool为true时,此相同功能仅应清除托管资源。添加终结器会增加使用对象的复杂性,因为每次创建新实例时,还必须将其放在终结队列中,每次GC运行收集周期时都会对其进行检查。实际上,这意味着对象比预期的生存时间长了一个周期/代,因此可以运行终结器。终结者不应被视为"安全网"。

GC仅在确定Gen0堆中没有足够的可用内存来执行下一次分配时才运行收集周期,除非我们通过调用GC.Collect()强制进行带外收集来"帮助"它。

最重要的是,无论如何,GC仅知道如何通过调用Dispose方法(如果实现了终结器,则可能是终结器)来释放资源。由该方法"做正确的事"并清除所有使用的非托管资源,并指示任何其他托管资源调用其Dispose方法。只要没有带外收集周期的帮助,它的工作效率就很高,并且可以在很大程度上进行自我优化。话虽如此,除非明确调用GC.Collect,否则我们无法控制对象的处置时间和顺序以及释放的内存。