Reputation: 3264
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
Commenting the usage of local variable(i=10) will fix the issue.Subscription code will execute as expected
Commenting GC.collect will fix the issue. Subscription code will execute as expected
What is the reason for this behavior?
Upvotes: 2
Views: 215
Reputation: 91
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
Upvotes: 1