none
Events Handlers keeping objects alive RRS feed

  • Question

  • Hello,

    I'd appreciate some suggestions how to solve the problem exposed below.
    I apologize in case that's too easy and I cannot see the solution myself.

    Thank you in advance.

    using System;

    namespace KeepAliveEventsExample
    {
        /// <summary>
        /// How to garbage-collect an instance which is kept alive only by
        /// event handlers without removing the handlers explicitly?
        ///
        /// Client runs a procedure. It holds a reference to an
        /// object able to raise an event. Inside the method (Run)
        /// an instance is created which can subsrcibe to the event.
        /// The event handler keeps the subscriber alive even if the subscriber
        /// reference is not reachable by the client.
        /// In order to allow the garbage collection an "Unregister()" method removes
        /// explicitly the event handler.
        /// We wish a solution in order to get to the same result without calling any explicit method
        /// like Unregister()
        /// </summary>
        /// <remarks>
        /// - No Dispose Pattern
        /// - No new Threads
        /// </remarks>
        class Program
        {
            static void Main(string[] args)
            {
                Client client = new Client();

                client.Run();

                Console.WriteLine("RUN FINISHED. PRESS A KEY");

                Console.ReadKey();

                client = null;

                Client.FullCollect("LAST COLLECTION");

                Console.WriteLine("FINISHED");

                Console.ReadKey();

            }
        }

        /// <summary>
        /// A client of publisher / subscriber
        /// It keeps a reference to the publisher but not to the subscriber
        /// </summary>
        /// <remarks>
        /// While a reference to the publisher is held, no reference
        /// to the subscriber is reachable outside the method "Run()"
        /// </remarks>
        class Client
        {
            /// <summary>
            /// The client holds a reference to the event publisher
            /// </summary>
            private ObjectWithEvents obj;

            public Client()
            {
                obj = new ObjectWithEvents();
            }

            public void Run()
            {
                Observer _subscriber = new Observer(obj);

                //we should see _subscriber reacting
                obj.DoSomething(5);
                obj.DoSomething(10);

                //we would like to eliminate this method
                _subscriber.Unregister();
                            
                            //the subscriber is not reacheable [only through the event in obj]
                _subscriber = null;

                //here we would like _Subscriber NOT TO REACT without having to do anything
                obj.DoSomething(15);

                //We would like to be able to collect _Subscriber here
                FullCollect("COLLECTION 1");

            }

            ~Client()
            {
                Console.WriteLine("FINALIZING CLIENT");
            }

            /// <summary>
            /// Try to trigger possible collections in all generations.
            /// </summary>
            /// <param name="message"></param>
            public static void FullCollect(string message)
            {
                for (int j = 0; j <= GC.MaxGeneration; j++)
                {
                    for (int i = 0; i <= GC.MaxGeneration; i++)
                    {
                        GC.Collect(i, GCCollectionMode.Forced);
                        GC.WaitForPendingFinalizers();
                    }
                }
                Console.WriteLine(message);
            }
        }
        /// <summary>
        /// Subscribes to the event of ObjectWithEvents
        /// </summary>
        /// <remarks>
        /// Instances of this object might be kept for the whole application lifetime
        /// or just used for performing some operations.
        ///
        /// Implementing the Dispose Pattern is not ok because in the real
        /// application this would have a deeper meaning for the user
        /// </remarks>
        class Observer
        {
            /// <summary>
            /// We can hold a reference to the publisher. This won't generate a problem
            /// Also shows that, if we want, we have the publishers available
            /// </summary>
            ObjectWithEvents _publisher;

            public Observer(ObjectWithEvents eventPublisher)
            {
                _publisher = eventPublisher;

                //here we register to the event. This instance is going to
                //survive garbage collection because the publisher keeps it alive
                //through this handler
                _publisher.ValueChanged += new EventDefinition(handler);

            }

            /// <summary>
            /// Unregister the event handler in order to allow GC
            /// We would like clients not to have to call this method
            /// </summary>
            public void Unregister()
            {
                _publisher.ValueChanged -= new EventDefinition(handler);
            }

            void handler(object sender, int value)
            {
                Console.WriteLine("VALUE CHANGED TO: {0}", value);
            }

            ~Observer()
            {
                Console.WriteLine("FINALIZING SUBSCRIBER");
            }
        }

        /// <summary>
        /// Event definition
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="value"></param>
        delegate void EventDefinition(object sender, int value);

        /// <summary>
        /// An object able to raise an event
        /// </summary>
        /// <remarks>
        /// Instances are unaware of subscribers
        /// </remarks>
        class ObjectWithEvents
        {
            int _value;

            public event EventDefinition ValueChanged;

            public void DoSomething(int value)
            {
                _value = value;

                //raise the event.
                if (ValueChanged != null)
                    ValueChanged(this, _value);
            }

            ~ObjectWithEvents()
            {
                Console.WriteLine("FINALIZING PUBLISHER");
            }
        }
    }

    Tuesday, October 7, 2008 10:19 PM

Answers

  • Solve your problem by adding an event to the Observer class.  It should subscribe to the ObjectWithEvents event and chain the event call on to its own event.  The delegate targets now have the same life time as the object instances.
    Hans Passant.
    • Marked as answer by Zhi-Xin Ye Monday, October 13, 2008 8:37 AM
    Wednesday, October 8, 2008 11:35 AM
    Moderator

All replies

  • There's no easy way to do what you want. You could invent your own event mechanism that relies on WeakReferences, but I don't think even that would take you all the way.

    Setting a reference to null, like you do in 

               //the subscriber is not reacheable [only through the event in obj]
                _subscriber = null;

    doesn't cause any cleanup code to run, so the event handler would still be registered.

    Why are you so concerned about the lifetime of the observer object? And what's wrong with having an Unregister method (or using the dispose pattern)?

    Mattias, C# MVP
    Wednesday, October 8, 2008 7:04 AM
    Moderator
  • Solve your problem by adding an event to the Observer class.  It should subscribe to the ObjectWithEvents event and chain the event call on to its own event.  The delegate targets now have the same life time as the object instances.
    Hans Passant.
    • Marked as answer by Zhi-Xin Ye Monday, October 13, 2008 8:37 AM
    Wednesday, October 8, 2008 11:35 AM
    Moderator