none
will garbage collector remove my object RRS feed

  • Question

  • Hello All,

    I'd like to understand how garbage collector work in following case (particulary in delegates)

    Here are some class definitions-

       class Manager
        {
           
            int m_age = 10;
            public void Register()
            {
                EventCallback callback = new EventCallback();
                callback.Register(new Notify(NotifyMethod));
               
            }
            public void NotifyMethod(String message)
            {
                Console.WriteLine(message);
                Console.WriteLine(m_age);

            }
        }
       
        class EventCallback
        {
            private static List<Notify> _callbacks = new List<Notify>();

            public void Register(Notify notify)
            {
                _callbacks.Add(notify);
            }

            public static void NotifyEveryOne()
            {
                foreach (Notify callback in _callbacks)
                {
                    callback("Hello");
                }
            }
           
        }

        public delegate void Notify(String msg);


    Now inside a method I write following code-


    public void register()
    {
                Manager manager = new Manager();
                manager.Register();
    }

    Now if after execution of "register" method garabage collector run then is it going to release "manager" object? If in some other part of application I wrote the code...

    EventCallback.NotifyEveryOne();

    What is going to happen? I did not see any problem even when I called GC.Collect() after "register" method.


    Thank you for giving your time.

    Regards,
    Gurmit

    Monday, November 30, 2009 2:02 PM

Answers

  • No.  Manager will NOT be garbage collected, unless you go through and remove it's delegate from your _callbacks static list.

    The delegate itself prevents the "manager" instance from becoming unrooted.  What you are doing here, is basically creating a static event.  You can search around, but events on static objects are one place where (pseudo) memory leaks are common in .NET.

    When you create a delegate, and hold it in a static object, that holds an instance to the class containing the delegate's method.  This will prevent manager from being garbage collected.

    There are three basic approaches to avoiding this. 

    1) If your "NotifyMethod" can be made static, you will prevent this.  Static methods, when used as delegates, don't keep a reference to an instance (just the type).  This will prevent the object from being collected.
    2) If you make an "unregister" function, you can have "manager" remove itself from the callback list.  The Disposable pattern can be useful here, since it would let you just write:

    using (Manager manager = new Manager())
    {
     // ... register, + do your work
    } // manager.Dispose() could "unregister" automatically here

    3) Use the WeakEvent pattern for your callback instead of a direct delegate call.  This lets you keep the lifetime of the two sides independent.








    Reed Copsey, Jr. - http://reedcopsey.com
    • Marked as answer by Gurmit Teotia Tuesday, December 1, 2009 5:19 AM
    Monday, November 30, 2009 4:15 PM
    Moderator
  • Seeing is believing.

    Run CLRProfiler.exe (http://www.microsoft.com/downloads/details.aspx?FamilyID=CD5AA57C-9CD1-45AB-AA4B-8DC586D30938&displayLang=en) and start your application. Display the heap (show heap now -> Detail=0). Type CTRL+F then enter Generic.List<T> to find the object on the heap. Right-click on it and select 'Filter to callers & callees'.

    Now you can see the whole reference path from Generic.List<T> -> Notify[] -> Notify -> Manager. *This* is what prevents the Manager object to get GC-ed. The static generic list references an array of Notify delegate objects (instances of IL-class Notify that extends System.MulticastDelegate) which references a notify delegate object which references a manager object.

    And if you want to get a more procedural view you can display the call tree view and drill down from Program::Main to EventCallback::NotifyEveryone to see GenericList<T>::GetEnumerator getting called, followed by a call to Enumerator::MoveNext and Enumerator::get_Current. Then Manager::NotifyMethod gets called.

    It's all there, kept alive by the static List<T> field.


    Marcel

    • Marked as answer by Gurmit Teotia Tuesday, December 1, 2009 5:20 AM
    Monday, November 30, 2009 10:23 PM

All replies

  • "manager" would be eligible for GC after the call to Register.

    However, VS helps you out: if compiling for Debug or running under the debugger, "manager" will exist until the end of register(). If you compile for Release and run outside the debugger, you should see "manager" collected by a GC.Collect.

            -Steve
    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ

    Microsoft Certified Professional Developer
    Monday, November 30, 2009 2:21 PM

  • Thank you for your response.

    I tried to use the code as follow-

          public static void register()
            {
                Manager manager = new Manager();
                manager.Register();
            }

            public static void Main()
            {
                register();
                GC.Collect();
                EventCallback.NotifyEveryOne();
                Console.Read();

            }

    I build the code in release mode and executed it directly from command prompt. Should not it crash now? But it is executed successfully and printing-

     "Hello"
      10   //hence accessing the "manager" object.

    If "manager" object is released should not it crash now?

    Monday, November 30, 2009 2:29 PM
  • No.  Manager will NOT be garbage collected, unless you go through and remove it's delegate from your _callbacks static list.

    The delegate itself prevents the "manager" instance from becoming unrooted.  What you are doing here, is basically creating a static event.  You can search around, but events on static objects are one place where (pseudo) memory leaks are common in .NET.

    When you create a delegate, and hold it in a static object, that holds an instance to the class containing the delegate's method.  This will prevent manager from being garbage collected.

    There are three basic approaches to avoiding this. 

    1) If your "NotifyMethod" can be made static, you will prevent this.  Static methods, when used as delegates, don't keep a reference to an instance (just the type).  This will prevent the object from being collected.
    2) If you make an "unregister" function, you can have "manager" remove itself from the callback list.  The Disposable pattern can be useful here, since it would let you just write:

    using (Manager manager = new Manager())
    {
     // ... register, + do your work
    } // manager.Dispose() could "unregister" automatically here

    3) Use the WeakEvent pattern for your callback instead of a direct delegate call.  This lets you keep the lifetime of the two sides independent.








    Reed Copsey, Jr. - http://reedcopsey.com
    • Marked as answer by Gurmit Teotia Tuesday, December 1, 2009 5:19 AM
    Monday, November 30, 2009 4:15 PM
    Moderator
  • Good catch. I read the code too fast and missed the "static" part. :)

           -Steve
    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ

    Microsoft Certified Professional Developer
    Monday, November 30, 2009 4:36 PM
  • Seeing is believing.

    Run CLRProfiler.exe (http://www.microsoft.com/downloads/details.aspx?FamilyID=CD5AA57C-9CD1-45AB-AA4B-8DC586D30938&displayLang=en) and start your application. Display the heap (show heap now -> Detail=0). Type CTRL+F then enter Generic.List<T> to find the object on the heap. Right-click on it and select 'Filter to callers & callees'.

    Now you can see the whole reference path from Generic.List<T> -> Notify[] -> Notify -> Manager. *This* is what prevents the Manager object to get GC-ed. The static generic list references an array of Notify delegate objects (instances of IL-class Notify that extends System.MulticastDelegate) which references a notify delegate object which references a manager object.

    And if you want to get a more procedural view you can display the call tree view and drill down from Program::Main to EventCallback::NotifyEveryone to see GenericList<T>::GetEnumerator getting called, followed by a call to Enumerator::MoveNext and Enumerator::get_Current. Then Manager::NotifyMethod gets called.

    It's all there, kept alive by the static List<T> field.


    Marcel

    • Marked as answer by Gurmit Teotia Tuesday, December 1, 2009 5:20 AM
    Monday, November 30, 2009 10:23 PM