Is GC.SuppressFinalize guaranteed?

c#, garbage-collection

Solution

One oddity you may be seeing is that the finalizer can still run even while an instance method is still running, so long as that instance method doesn't use any variables later on. So in your sample code, the `Dispose` method doesn't use any instance variables after the first line. The instance can then be finalized, even though `Dispose` is still running.

If you insert a call to `GC.KeepAlive(this)` at the end of the `Dispose` method, you may find the problem goes away.

Chris Brumme has a blog post about this, and I think there's another around somewhere...

Problem

My observation in practice has been that `GC.SuppressFinalize` does not always suppress the call to the finalizer. It could be that the finalizer gets called nontheless. I wonder therefore whether `GC.SuppressFinalize` has the nature of a request rather than a guarantee by the system? More Information Following information may help provide more context for the quesiton if needed. The `GC.SuppressFinalize` document summary does state that is a request: Requests that the system not call the finalizer for the specified object. I wonder if this was a casual use of the word or truly intended to describe the run-time behavior. I have observed this with the following `SingletonScope` class taken from the Schnell project, which was based on an original idea by Ian Griffiths except it is more generalized. The idea is to detect, in debug builds, if the `Dispose` method did get called or not. If not, the finalizer would kick in eventually and one can put up a warning. If `Dispose` is called then `GC.SuppressFinalize` should prevent the finalizer from firing. Unfortunately, the warnings seem to fire anyhow, but not in a deterministic fashion. That is, they don't fire on each and every run. ``` #region License, Terms and Author(s) // // Schnell - Wiki widgets // Copyright (c) 2007 Atif Aziz. All rights reserved. // // Author(s): // Atif Aziz, http://www.raboof.com // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation; either version 2.1 of the License, or (at // your option) any later version. // // This library is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public // License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with this library; if not, write to the Free Software Foundation, // Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // #endregion namespace WikiPad { #region Imports using System; using System.Diagnostics; #endregion // // NOTE: To use SingletonScope and ISingletonScopeHelper with value // types, use Nullable<T>. For example, if the type of value to scope // is ThreadPriority then use ISingletonScopeHelper<ThreadPriority?> // and SingletonScope<ThreadPriority?>. // // // In debug builds, this type is defined as a class so a finalizer // can be used to detect an undisposed scope. // /// <summary> /// Designed to change a singleton and scope that change. After exiting /// the scope, the singleton is restored to its value prior to entering /// the scope. /// </summary> #if !DEBUG internal struct SingletonScope<T, H> #else internal sealed class SingletonScope<T, H> #endif : IDisposable where H : ISingletonScopeHelper<T>, new() { private T _old; public SingletonScope(T temp) { _old = Helper.Install(temp); } private static H Helper { get { return new H(); } } public void Dispose() { // // First, transfer fields to stack then nuke the fields. // var old = _old; _old = default(T); // // Shazam! Restore the old value. // Helper.Restore(old); #if DEBUG GC.SuppressFinalize(this); // Only when defined as a class! #endif } #if DEBUG // // This finalizer is used to detect an undisposed scope. This will // only indicate that the scope was not disposed but (unfortunately) // not which one and where since GC will probably collect much later // than it should have been disposed. // ~SingletonScope() { Debug.Fail("Scope for " + typeof(T).FullName + " not disposed!"); } #endif } } ``` A full working example is available at http://gist.github.com/102424 with compilation instructions, but do note that the problem cannot be reproduced deterministically so far.

Original source