UPDATE: For a fully featured and tested Mediator implementation, check out the Messenger class in my MVVM Foundation library.
This blog post shows a prototype implementation of the Mediator design pattern, intended to be used in WPF and, possibly, Silverlight applications. The Mediator class can be used to provide loosely-coupled message-based communication between various entities, such as ViewModel objects. The prototype was inspired by this thread on the WPF Disciples group, and particularly by conversations I had with Marlon Grech. I am posting this prototype on my blog, in hopes of receiving suggestions and new ideas on how to improve the design.
The code reviewed in this blog post works, but is not complete or intended to be used in production applications.
Creating a class that allows various objects to send messages to each other is trivial. It becomes more challenging when you try to create a reusable class like that which does not leak memory. Since the Mediator object can live indefinitely, but the objects registered to receive messages might have a shorter lifespan, this could easily become a hotspot for memory leaks to occur. In an ideal world, any object that registers itself to receive messages should unregister itself when it no longer needs to receive messages. However, in practice, it’s not always easy to know when an object should unregister itself. The design of the Mediator class needs to take this into consideration.
Let’s take a quick look at how this Mediator class might be used. Here’s some code showing how an object registers itself to receive a certain message…
Notice that registering for a message requires you to pass in two parameters. The first parameter is a unique identifier for that message, in this case a string exposed by the MediatorMessages class. The second parameter is a delegate of type Action<object>. In the example above I use a lambda expression to create the delegate, but you could just as easily create a new Action<object> instance, if you’d prefer.
Here is an example of an object sending its colleagues a message:
So far, so good, so it seems. However, there is a problem. A delegate, such as Action<object>, stores a reference to the object on which the target method should be invoked. When you pass that delegate to the Mediator, it stores the delegate in a collection, for later use. If you never remove the delegate from the Mediator, via unregistering (which is something I have yet to implement in the prototype), the registered object will never be eligible for garbage collection since there will be a hard reference to it. Hence, the memory leak issue I mentioned earlier.
My first thought was to store a reference to the Action<object> in a WeakReference. Since the Garbage Collector will throw away objects that are referenced only by WeakReference objects, it would seem that this would do the trick. Unfortunately, it’s not that simple. The problem with that approach is that the GC will throw away the Action<object> instance because it is only being referenced by a WeakReference!
Marlon came up with a great idea, which borrows Greg Schechter’s idea of subclassing WeakReference to make a “weak delegate.” This is essentially a delegate that uses a WeakReference to store a reference to the target object, instead of using a normal object reference, as normal delegates do. The drawback to Marlon’s great idea is that it relies on the use of reflection to invoke the target method on the colleague. This means that it will have a performance decrease, and won’t run in partial trust.
I took Marlon’s idea, and refined it so that it does not require the use of reflection (at least, not directly). My implementation uses a class I called WeakAction, which looks like this:
The collection used by Mediator, which maps a Message to a list of Action<object>, is called MessageToActionsMap. That class uses the WeakAction type, as seen below:
As you can see in the following Mediator method, this implementation detail does not bubble up into the public-facing API:
You can download the prototype source code and demo app here: Mediator Prototype and Demo . NOTE: Be sure to change the file extension from .DOC to .ZIP, and then decompress it.
If you have any suggested improvements, bugs, etc. please don’t hesitate to let me know.