C#析构函数未按预期工作

时间:2010-08-17 05:31:24

标签: c# garbage-collection destructor

请参阅下面的代码。我希望它打印10,因为我已经显式调用了垃圾收集器。但我总是输出0或20作为输出。那是为什么?

void Main()
{
    Panda[] forest_panda = new Panda[10];
    for(int i=0; i<forest_panda.GetLength(0);i++)
    {
        forest_panda[i]=new Panda("P1");
    }

    for(int i=0; i<forest_panda.GetLength(0);i++)
    {
        forest_panda[i]=new Panda("P1");
    }

    System.GC.Collect();

    Console.WriteLine("Total Pandas created is {0}",Panda.population);          
}

class Panda
{
    public static int population=0;
    public string name;

    public Panda(string name)
    {
        this.name = name;
        population = population + 1;
    }

    ~Panda()
    {
        population = population - 1;
    }   
}

请注意,Main的类是由LINQPad(“Nutshell中的C#4.0”一书中的编辑器)自动创建的。我是C#的新手。

8 个答案:

答案 0 :(得分:7)

您尚未运行explict垃圾回收。来自GC.Collect()的文档:

  

使用此方法尝试回收   所有无法访问的内存。   但是,Collect方法没有   保证所有无法访问的内存   被收回。

     

所有对象,无论多长时间   他们一直在记忆中   考虑收集;然而,   托管中引用的对象   代码未收集。用这个   强制系统尝试的方法   收回最高金额   可用内存。

garabage收集器经过高度优化,当他实际进行垃圾收集然后调用终结器时,他自己“决定”所有人。此外,它都是异步完成的。这也是Finalizer被称为非确定性清理的原因。你现在从未做过清理工作。

您现在有两种选择。您可以调用GC.WaitForPendingFinalizers()将停止当前线程,直到所有可终结对象完成为止。或者调用这个新的重载:System.GC.Collect(int generation, System.GCCollectionMode mode)GCCollectionMode.Forced它是在.NET 3.5中引入的。

请记住,通常没有必要,更重要的是:一个坏主意手动调用垃圾收集器。只有在极少数情况下才需要实现终结器。调用垃圾收集器会降低运行时间。实现终结器还会减慢运行时间。当准备好收集garabge时,garabge收集器将实现终结器的所有对象放入终结队列。处理此队列非常昂贵。 更糟糕的是,当终结器运行时,无法保证您尝试访问的成员仍然存在。他们很可能已经收集了垃圾。这就是为什么只有当你需要清理 非托管 资源时才应该使用终结器。

在您的示例中,绝对不需要所有这些。对于确定性清理,你真正想要的是IDisposable

答案 1 :(得分:6)

这里有几点需要注意:

首先,GC在发布和调试版本之间的行为有所不同。通常,在发布模式下,可以比在调试模式下更快地回收对象。

正如蒂姆指出的那样,调用GC.Collect不会调用终结器。如果你想等终结器运行,也可以调用GC.WaitForPendingFinalizers。

终结器由专用线程运行,因此您实际上是在没有任何同步的情况下从两个不同的线程修改状态。虽然在这种特殊情况下这可能不是问题,但这样做并不是一个好主意。但是在你去为终结器添加同步之前,请记住,死锁的终结器意味着不再运行终结器,因此不会回收这些对象的内存。

答案 2 :(得分:3)

在垃圾回收后尝试添加System.GC.WaitForPendingFinalizers http://www.developer.com/net/csharp/article.php/3343191/C-Tip-Forcing-Garbage-Collection-in-NET.htm

答案 3 :(得分:2)

您创建了20个对象,因此该值将为20.显式调用System.GC.Collect()实际上并不能保证调用析构函数。因此,如果它被调用,则所有20个对象可能已经被破坏或者没有被破坏。

这解释了实际发生的事情。

创建析构函数或明确调用GC.Collect不是一个好习惯。

如果一个对象需要进行清理,它应该实现IDisposable

答案 4 :(得分:1)

在.NET中,对象生命周期为non-deterministic,并且不像C ++构造函数/析构函数那样表现。实际上,.NET对象在技术上并不具有析构函数。终结器的不同之处在于,它应该清理对象在其生命周期中使用的非托管资源。

要使用确定性方法释放对象使用的资源,可以实现IDisposable接口。 IDisposable并不完美,因为它仍然需要调用代码在完成后正确处理对象,并且很难处理对Dispose的意外多次调用。但是,C#中的语法通常很容易。

class Panda : IDisposable
{
    public static int population = 0;
    public string _name;

    public Panda( string name )
    {
        if( name == null )
            throw new ArgumentNullException( name );

        _name = name;
        population++;
    }

    protected virtual void Dispose( bool disposing )
    {
        if( disposing && name != null )
        {
            population--;
            name = null;
        }
    }

    public void Dispose()
    {
        Dispose( true );
        GC.SuppressFinalize( this );
    }

    ~Panda(){ Dispose( false ); }
}

然后使用该课程:

using( var panda = new Panda( "Cute & Cuddly" ) )
{
    // Do something with the panda

} // panda.Dispose() called automatically

答案 5 :(得分:1)

使用析构函数(a.k.a。终结器)并不是用C#做事的好方法。即使您明确调用垃圾收集器,也无法保证终结器能够运行。您也不应该尝试强制垃圾收集,因为它可能会对您的整个应用程序产生负面性能影响。

相反,如果需要显式释放对象拥有的资源,则应实现IDisposable接口,并将清理逻辑放在Dispose()方法中。相反,当您使用实现IDisposable的对象时,在完成它时应始终注意调用其Dispose()方法。 C#为此提供了“using”语句。

许多执行I / O的类(例如Streams)实现了IDisposable。以下是使用FileStream读取文本文件的示例。注意“using”语句以确保在完成FileStream时将其处理:

using (FileStream fs = File.OpenRead("C:\\temp\\myfile.txt"))
{
    // Read a text file 1024 bytes at a time and write it to the console
    byte[] b = new byte[1024];
    while (fs.Read(b, 0, b.Length) > 0)
    {
        Console.WriteLine(Encoding.UTF8.GetString(b));
    }
} // Dispose() is called automatically here

以上代码与此相同:

FileStream fs = File.OpenRead("C:\\temp\\myfile.txt"))
try
{
    // Read a text file 1024 bytes at a time and write it to the console
    byte[] b = new byte[1024];
    while (fs.Read(b, 0, b.Length) > 0)
    {
        Console.WriteLine(Encoding.UTF8.GetString(b));
    }
}
finally
{
    fs.Dispose();
}

答案 6 :(得分:1)

处置模式最适合使用。 这是您工作的全部实施。

请记住,您必须自己调用Dispose,就像在下面的代码中一样。

    public static void Main()
    {
        Panda[] forest_panda = new Panda[10];
        for (int i = 0; i < forest_panda.GetLength(0); i++)
            forest_panda[i] = new Panda("P1");

        // Dispose the pandas by your own
        foreach (var panda in forest_panda)
            panda.Dispose();


        for (int i = 0; i < forest_panda.GetLength(0); i++)
            forest_panda[i] = new Panda("P1");

        // Dispose the pandas by your own
        foreach (var panda in forest_panda)
            panda.Dispose();

        Console.WriteLine("Total Pandas created is {0}", Panda.population);
    }

    class Panda : IDisposable
    {
        public static int population = 0;
        public string name;

        public Panda(string name)
        {
            this.name = name;
            population = population + 1;
        }

        ~Panda()
        {
            Dispose(false);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                population = population - 1;
            }
        }
    }

答案 7 :(得分:0)

它在release build中为我调用。但是在调试中的行为有所不同。