0
votes

I have a simple event defined using PRISM event aggregator pattern

 public class TestEvent : PubSubEvent
    {
    }
    public static class PrismEvents
    {
        public static readonly IEventAggregator EventAggregator = new EventAggregator();
        public static readonly TestEvent EventTest = EventAggregator.GetEvent<TestEvent>();
    }

I have a subscriber class where this event is subscribed using a lambda.Note the usage of a local variable(i) inside the subscription code

public class SubScriber
    {
        public SubScriber()
        {
            int i = 5;
            PrismEvents.EventTest.Subscribe(() =>
            {
                Console.WriteLine("Event Fired");//not getting called
                i = 10; //commenting this line will execute the subscription code
            });
        }

    }

In the publisher side Subscriber is created, then GC is called then event publised.

Subscription code is not getting executed!

 class Program
    {
        static void Main(string[] args)
        {
            new SubScriber();
            GC.Collect(); //commenting this line will execute the subscription code
            PrismEvents.EventTest.Publish();
            Console.ReadKey();
        }

    }

Couple of points

  1. Commenting the usage of local variable(i=10) will fix the issue.Subscription code will execute as expected

  2. Commenting GC.collect will fix the issue. Subscription code will execute as expected

What is the reason for this behavior?

1
I don’t know how C# does this, but considering how Java handles capturing and non-capturing lambdas, the SubScriber instance is entirely irrelevant, but a non-capturing lambda expression can be reused, hence, linked to the code creating it, which will use the same object each time you’re executing it, which will prevent its garbage collection. In contrast, a lambda expression capturing the current i variable has to produce a new instance each time the code is executed. Hence, the code will not keep a reference to it and it can be collected immediately after.Holger

1 Answers

0
votes

Good questions. I don't have all the answers, but Prism uses WeakReference. Subscribe create a WeakReference on the delegate (action) use in argument. To be precise, the WeakReference is made on the Target of the delegate. Here some code to understand better what happens :

    public class SubScriber
    {
        public SubScriber()
        {
            int i = 5;
            Action action1 = () =>
            {
                Console.WriteLine("Event Fired action1");//not getting called
                i = 11; //commenting this line will execute the subscription code
            };

            Action action2 = () =>
            {
                Console.WriteLine("Event Fired action2");//will be called
            };

            Console.WriteLine("Target 1 = "+ action1.Target);
            Console.WriteLine("Target 2 = " + action2.Target);
            PrismEvents.EventTest.Subscribe(action1);
            PrismEvents.EventTest.Subscribe(action2);
        }

        ~SubScriber()
        {
            Console.WriteLine("SubScriber destructed");
        }
    }

    static void Main(string[] args)
    {
        new SubScriber();
        GC.Collect(); //commenting this line will execute the subscription code
        GC.WaitForPendingFinalizers(); // or Thread.Sleep(2000);
        Console.WriteLine("Publish");
        PrismEvents.EventTest.Publish();
        Console.WriteLine("Press a key to finish");
        Console.ReadKey();
    }

We saw that the "SubScriber destructed" is displayed before "Publish". What is interesting is also to use ILSpy to see what is generated :

public class SubScriber
    {
        [CompilerGenerated]
        private sealed class <>c__DisplayClass0_0
        {
            public int i;

            internal void ctor>b__0()
            {
                Console.WriteLine("Event Fired action1");
                this.i = 11;
            }
        }

        [CompilerGenerated]
        [Serializable]
        private sealed class <>c
        {
            public static readonly Program.SubScriber.<>c <>9 = new Program.SubScriber.<>c();

            public static Action <>9__0_1;

            internal void ctor>b__0_1()
            {
                Console.WriteLine("Event Fired action2");
            }
        }

        public SubScriber()
        {
            Program.SubScriber.<>c__DisplayClass0_0 <>c__DisplayClass0_ = new Program.SubScriber.<>c__DisplayClass0_0();
            <>c__DisplayClass0_.i = 5;
            Action action = new Action(<>c__DisplayClass0_.<.ctor>b__0);
            Action arg_41_0;
            if ((arg_41_0 = Program.SubScriber.<>c.<>9__0_1) == null)
            {
                arg_41_0 = (Program.SubScriber.<>c.<>9__0_1 = new Action(Program.SubScriber.<>c.<>9.<.ctor>b__0_1));
            }
            Action action2 = arg_41_0;
            string arg_59_0 = "Target 1 = ";
            object expr_4D = action.Target;
            Console.WriteLine(arg_59_0 + ((expr_4D != null) ? expr_4D.ToString() : null));
            string arg_7B_0 = "Target 2 = ";
            object expr_6F = action2.Target;
            Console.WriteLine(arg_7B_0 + ((expr_6F != null) ? expr_6F.ToString() : null));
            Program.PrismEvents.EventTest.Subscribe(action);
            Program.PrismEvents.EventTest.Subscribe(action2);
        }

        ~SubScriber()
        {
            Console.WriteLine("SubScriber destructed");
        }
    }

The difference I saw is that the 2nd action has a static readonly field that hold an instance on the delegate...

Regards, Sybaris