To help reduce dependencies between my ViewModels, I am trying to create my own Messenger service.
Here is some code:
public struct Subscription
{
public Type Type { get; set; }
public string Message { get; set; }
//Error: Cannot implicitly convert type 'System.Action<TPayload>' to 'System.Action'
public Action Callback { get; set; }
}
public static class Messenger
{
private static List<Subscription> Subscribers { get; set; }
static Messenger()
{
Subscribers = new List<Subscription>();
}
public static void Subscribe<TPayload>(string message, Action<TPayload> callback)
{
//Add to the subscriber list
Subscribers.Add(new Subscription()
{
Type = typeof(TPayload),
Message = message,
Callback = callback
});
}
public static void Send<TPayload>(string message, TPayload payload)
{
//Get all subscribers that match the message and payload type
IEnumerable<Subscription> subs = Subscribers.Where(x => x.Message == message && x.Type == typeof(TPayload));
//Invoke the callback and send the payload.
foreach (Subscription sub in subs)
sub.Callback.Invoke(payload);
}
}
Here's a low down on what the hell is going on here:
The Messenger
class is responsible for receiving subscriptions to a message, the subscriber must specify the return type of the expected payload that they will receive, and the message string that they are subscribing to.
The subscribe method deals with the subscriptions and the send method will invoke the Callback
property and send the payload to any subscribers.
The problem that I'm having is that Action<T>
is a delegate and has no base inheritance. I cannot simply add a generic to the Subscription
struct as that would make the List<Subscription>
a List<Subscription<T>>
which will screw things up.
One thing to note is that I will also be allowing subscriptions without payloads in the future. I'm trying to get my head around how I can achieve this.
EDIT Using Matt's code, I have adapted it to suit my requirements. Here it is if anyone is interested.
public interface ISubscriber
{
string Message { get; set; }
void InvokeMethod(object args);
}
public class Subscriber : ISubscriber
{
public string Message { get; set; }
public Action Callback { get; set; }
public virtual void InvokeMethod(object args = null)
{
Callback.Invoke();
}
}
public class Subscriber<T> : Subscriber
{
new public Action<T> Callback { get; set; }
public override void InvokeMethod(object payload)
{
if (!(payload is T))
throw new ArgumentException(String.Concat("Payload is not of type: ", typeof(T).Name), "payload");
Callback.Invoke((T)payload);
}
}
public static class Messenger
{
private static List<ISubscriber> Subscribers { get; set; }
static Messenger()
{
Subscribers = new List<ISubscriber>();
}
public static void Subscribe(string message, Action callback)
{
//Add to the subscriber list
Subscribers.Add(new Subscriber()
{
Message = message,
Callback = callback
});
}
public static void Subscribe<TPayload>(string message, Action<TPayload> callback)
{
//Add to the subscriber list
Subscribers.Add(new Subscriber<TPayload>()
{
Message = message,
Callback = callback
});
}
public static void Send(string message)
{
//Invoke the Callback for all subscribers
foreach (Subscriber sub in GetSubscribers(message))
sub.InvokeMethod();
}
public static void Send<TPayload>(string message, TPayload payload)
{
//Invoke the TypedCallback for all subscribers
foreach (ISubscriber sub in GetSubscribers(message))
sub.InvokeMethod(payload);
}
private static IEnumerable<ISubscriber> GetSubscribers(string message)
{
//Get all subscribers by matching message.
return Subscribers.Where(x => x.Message == message);
}
}
The best way to approach this is through the use of an interface with a generic method to execute the Action for you.
It can then be used like this.