Creating a Messenger service

2019-07-18 13:16发布

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);
    }
}

1条回答
爷、活的狠高调
2楼-- · 2019-07-18 13:41

The best way to approach this is through the use of an interface with a generic method to execute the Action for you.

public interface ISubscription
{
    Type Type { get;}
    String Message { get; set; }
    void InvokeMethod(object args);
}


public class Subscription<T> : ISubscription
{
    public Type Type { get { return typeof(T); } }
    public string Message { get; set; }

    public Action<T> TypedCallback { get; set; }

    void ISubscription.InvokeMethod(object args)
    {
        if (!(args is T))
        {
            throw new ArgumentException(String.Concat("args is not type: ", typeof(T).Name), "args");
        }
        TypedCallback.Invoke((T)args);
    }
}

public static class Messenger
{
    private static List<ISubscription> Subscribers { get; set; }

    static Messenger()
    {
        Subscribers = new List<ISubscription>();
    }

    public static void Subscribe<TPayload>(string message, Action<TPayload> callback)
    {
        //Add to the subscriber list
        Subscribers.Add(new Subscription<TPayload>()
        {
            Message = message,
            TypedCallback = callback
        });
    }

    public static void Send<TPayload>(string message, TPayload payload)
    {
        //Get all subscribers that match the message and payload type
        IEnumerable<ISubscription> subs = Subscribers.Where(x => x.Message == message && x.Type == typeof(TPayload));

        foreach (ISubscription sub in subs)
            sub.InvokeMethod(payload);

    }
}

It can then be used like this.

class Program
{
    static void Main(string[] args)
    {
        Action<String> StringAction = new Action<string>((a) => WriteString(a));
        Action<Int32> Int32Action = new Action<Int32>((a) => WriteString(a.ToString()));

        Messenger.Subscribe<String>("Sub1", StringAction);
        Messenger.Send<String>("Sub1", "I am a string");

        Messenger.Subscribe<Int32>("Sub2", Int32Action);
        Messenger.Send<Int32>("Sub2", 72);

        Console.ReadLine();
    }

    private static String WriteString(String message)
    {
        Console.WriteLine(message);
        return message;
    }

}
查看更多
登录 后发表回答