首页 / 知识

关于内存:如何在.net中配置一个类?

2023-04-12 06:56:00

关于内存:如何在.net中配置一个类?

How to dispose a class in .net?

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

1
MyClass.Dispose()

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


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

好。

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

好。

编辑:赢得了大量业力的答案,我感到有责任对其进行详细阐述,以免.NET资源管理的新手获得错误的印象。

好。

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

好。

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

好。

  • VB和C#中的IDisposable接口和" using"语句
  • 终结者
  • 由许多BCL类实现的IDisposable模式
  • 首先,程序员可以有效地获取资源,使用资源,然后在同一方法中释放所有资源。

    好。

    1
    2
    3
    4
    5
    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程序员都应该理解的极其重要模式的一部分。

    好。

    好。


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

    要强制垃圾回收立即释放(非托管)内存:

    1
    2
    GC.Collect();  
    GC.WaitForPendingFinalizers();

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

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

    1
    instance.Dispose();

    或像这样:

    1
    2
    3
    4
    using(MyClass instance = new MyClass())
    {
        // Your cool code.
    }

    在编译时将转换为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    MyClass instance = null;    

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

    您可以像这样实现IDisposable接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    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;
        }
    }

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

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

    .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的局部变量,请确保在当前作用域中将其删除:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //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属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //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仍可以清除"危险"非托管资源。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    ~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的重载带有一个布尔标志:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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)。


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

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


    看看这篇文章

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

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

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

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

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


    1
    2
    3
    4
    5
    6
    7
    public class MyClass : IDisposable
    {
        public void Dispose()
        {
           // cleanup here
        }
    }

    那么你可以做这样的事情

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

    要么

    1
    2
    3
    4
    5
    using (MyClass instance = new MyClass())
    {

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

    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.


    我在http://codingcraftsman.wordpress.com/2012/04/25/to-dispose-or-not-to-dispose/上撰写了《析构函数与处置和垃圾收集》的摘要。

    要回答原始问题:

  • 不要试图管理你的记忆
  • 处理不是关于内存管理,而是关于非托管资源管理
  • 终结器是Dispose模式的固有部分,实际上减慢了托管对象的内存释放(因为它们必须进入Finalization队列,除非已经Dispose d)
  • GC.Collect不好,因为它会使一些短寿命的对象看起来需要更长的时间,从而减慢了它们的收集速度。
  • 但是,如果您的代码中有一个性能至关重要的部分,并且想减少垃圾回收速度变慢的可能性,GC.Collect可能会很有用。你先打过电话

    最重要的是,有人赞成这种模式:

    1
    2
    3
    4
    var myBigObject = new MyBigObject(1);
    // something happens
    myBigObject = new MyBigObject(2);
    // at the above line, there are temporarily two big objects in memory and neither can be collected

    1
    2
    myBigObject = null; // so it could now be collected
    myBigObject = new MyBigObject(2);

    但主要的答案是,除非您弄乱了垃圾回收,否则垃圾回收将正常工作!


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


    @基思

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

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

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


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

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

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

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


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

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


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

    1
    MyClass.Dispose();

    C#的最佳做法是:

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

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


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

    1
    GC.Collect();


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

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

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


    @Curt Hagenlocher-回来了。我不知道为什么有那么多人在错误的地方投了赞成票。

    IDisposable用于托管资源。

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

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

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

    避免使用GC.Collect-这是一种处理托管资源的缓慢方法,除非您已正确构建?Finalizers,否则它不会对非托管资源执行任何操作。

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


    @基思:

    IDisposable is for managed resources.

    Finalisers are for unmanaged resources.

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

    Dispose有两个作业:

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

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

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


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

    然后,当您实现它时,就使用Microsoft的处置模式(如前所述)

    • public Dispose()调用protected Dispose(true)-处理托管和非托管资源。调用Dispose()应该抑制完成。

    • ~Finalize调用protected Dispose(false)-仅处理非托管资源。如果您无法调用public Dispose(),这可以防止非托管内存泄漏

    ~Finalize很慢,除非您有非托管资源要处理,否则不应该使用它。

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

    无论哪种情况,Using都是最佳实践。


    在回答原始问题时,利用原始发布者到目前为止提供的信息,可以100%确信他对.NET编程的了解不足,甚至无法获得答案:使用GC.Collect()。我要说的是,正如大多数发帖人所指出的那样,他实际上根本不需要使用GC.Collect()的可能性为99.99%。

    正确的答案归结为"让GC做好工作。期。您还有其他需要担心的事情。但您可能要考虑是否以及何时应该处置或清理特定对象,以及是否需要在类中实现IDisposable以及Finalize。

    关于Keith的职位和他的规则4:

    一些张贴者混淆了规则3和规则4。基思的规则4绝对正确,毫不含糊。这是四个根本不需要编辑的规则。我会稍微改一下他的一些其他规则,以使它们更清晰,但是如果您正确地解析它们,并且实际上阅读了整篇文章,以了解他在这些规则上的扩展,那么它们在本质上是正确的。

  • 如果您的类不使用非托管资源,并且也从未实例化其本身直接或最终使用非托管对象的类的另一个对象(即,实现IDisposable的类),那么您的类就不需要了实现IDisposable本身,甚至调用.dispose进行任何操作。 (在这种情况下,认为您实际上确实需要立即通过强制GC释放内存是很愚蠢的。)

  • 如果您的类使用非托管资源,或者实例化了另一个本身实现IDisposable的对象,则您的类应:

    a)在创建它们的本地环境中立即处置/释放它们,或者...

    b)以Keith帖子中推荐的模式或互联网上的数千个位置或到目前为止的大约300本书的方式实现IDisposable。

    b.1)此外,如果(b)且它是已打开的非托管资源,则根据Keith规则4,应同时实现IDisposable和Finalize。
    在这种情况下,从某种意义上说,Finalize绝对是一个安全网:如果有人实例化使用非托管资源的IDisposable对象,但他们未能调用Dispose,则Finalize是您的对象正确关闭非托管资源的最后机会。
    (完成此操作的方式应该是通过调用Dispose的方式进行,以使Dispose方法跳过释放任何非托管资源的操作。或者,如果实例化的对象正确调用了您对象的Dispose方法,那么这两者都将Dispose调用传递给所有已实例化的IDisposable对象,并正确释放非托管资源,最后以抑制Finalize对您的对象的调用结束,这意味着如果调用方正确放置了对象,则使用Finalize的影响会降低。包含在Keith的帖子中,BTW。)

    b.2)如果您的类仅实现IDisposable,因为它实际上需要将Dispose传递给已实例化的IDisposable对象,则在这种情况下不要在类中实现Finalize方法。 Finalize用于处理以下情况:实例化对象从未调用过Dispose,并且仍然使用了尚未释放的非托管资源。

  • 简而言之,对于基思的帖子,他是完全正确的,我认为该帖子是最正确,最完整的答案。他可能会使用一些简短的陈述,有些人会发现"错误"或反对,但他的完整文章完全扩展了Finalize的用法,他是绝对正确的。在跳到他的帖子中的规则或初步声明之一之前,请务必完整阅读他的帖子。


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


    内存释放配置收回

    最新内容

    相关内容

    热门文章

    推荐文章

    标签云

    猜你喜欢