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

请参阅下面的代码。 我希望它打印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#的新手。

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

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

所有对象,无论它们在记忆中存在多长时间,都被考虑收集; 但是,不会收集托管代码中引用的对象。 使用此方法强制系统尝试回收最大可用内存量。

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

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

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

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

这里有几点需要注意:

首先,GC在发布和调试版本之间表现不同。 通常,在释放模式下,可以比在调试模式下更快地回收对象。

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

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

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

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

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

创建析构函数或显式调用GC.Collect不是好习惯。

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

在.NET中,对象生命周期是非确定性的,并且不像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 

使用析构函数(也就是终结器)并不是用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(); } 

处置模式将是最好的使用。 这是您工作的全面实施。

请记住,您必须自己调用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); } ///  /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. ///  /// 2 public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (disposing) { population = population - 1; } } }