Well, I now understand why it's happening (I believe, anyway). I've reproduced it in a shorter form which doesn't use lambda expressions, and then I'll explain why the lambdas are important.
using System;
using GalaSoft.MvvmLight.Messaging;
class Program
{
static void Main(string[] args)
{
Receiver r1 = new Receiver("r1");
Receiver r2 = new Receiver("r2");
var recipient = new object();
Messenger.Default.Register<object>(recipient, r1).ShowMessage;
Messenger.Default.Register<object>(recipient, r2).ShowMessage;
GC.Collect();
Messenger.Default.Send(recipient, null);
// Uncomment one of these to see the relevant message...
// GC.KeepAlive(r1);
// GC.KeepAlive(r2);
}
}
class Receiver
{
private string name;
public Receiver(string name)
{
this.name = name;
}
public void ShowMessage(object message)
{
Console.WriteLine("message received by {0}", name);
}
}
Basically, the messenger only keeps a weak reference to the message handler. (Also to the recipient, but that's not a problem here.) More specifically, it appears to have a weak reference to the handler's target object. It doesn't seem to care about the delegate object itself, but the target is important. So in the above code, when you keep a Receiver
object alive, the delegate which has that object as a target is still used. However, when the target is allowed to be garbage collected, the handler using that object is not used.
Now let's look at your two handler:
public void RegisterMessageA(object target)
{
Messenger.Default.Register(target, (Message msg) =>
{
Console.WriteLine(msg.Name + " received by A");
var x = target;
});
}
This lambda expression captures the target
parameter. In order to capture it, the compiler generates a new class - so RegisterMessageA
is effectively:
public void RegisterMessageA(object target)
{
GeneratedClass x = new GeneratedClass();
x.target = target;
Messenger.Default.Register(x.target, x.Method);
}
private class GeneratedClass
{
public object target;
public void Method(Message msg)
{
Console.WriteLine(msg.Name + " received by A");
var x = target;
}
}
Now, there's nothing other than the delegate which keeps that instance of GeneratedClass
alive. Compare that with your second handler:
public void RegisterMessageB(object target)
{
Messenger.Default.Register(target, (Message msg) =>
{
Console.WriteLine(msg.Name + " received by B");
});
}
Here, there are no captured variables, so the compiler generates code a bit like this:
public void RegisterMessageB(object target)
{
Messenger.Default.Register(target, RegisterMessageB_Lambda);
}
private static void RegisterMessageB_Lambda(Message msg)
{
Console.WriteLine(msg.Name + " received by B");
}
Here it's a static method, so there's no delegate target at all. If the delegate captured this
, it would be generated as an instance method. But the important point is that there's no need to generate an extra class... so there's nothing to be garbage collected.
I haven't looked into exactly how MvvmLight is doing this - whether it's just got a weak reference to the delegate, and that the CLR is treating that in some special way, or whether MvvmLight is separating the target from the delegate itself. Either way, I hope that explains the behaviour you're seeing. In terms of how to fix whatever problem you're seeing with real code - basically you'll need to make sure you keep a strong reference to whatever delegate target you need.
EDIT: Okay, it looks like it's now due to WeakActionGeneric
and its base class WeakAction
. I don't know whether this behaviour is the expected behaviour (by the author), but that's the code responsible :)