3
\$\begingroup\$

I have a handful of objects that don't have references to each-other that all need to receive an update.

From what I've read, it seems like a Publish-Subscribe Messaging Channel is an appropriate pattern. Searching for an existing solution mainly turned up messaging libraries for application integration (between processes). Those solutions were much more than what I needed, so I wrote this simple Publisher.

Questions:

  • Is there already something simple like this in the .NET framework that I overlooked? Or another light-weight library?
  • Is this an appropriate implementation and application of the Pub/Sub pattern? What might be done to improve it?
  • What gotchas are there? For example, I presume I need to Unsubscribe in the subscriber's finalizer.

public interface ISubscriber
{
    void SubscriptionUpdate(object message);
}

public interface IPublisher
{
    void Subscribe(ISubscriber subscriber, string channel);
    void Unsubscribe(ISubscriber subscriber, string channel);
    void Publish(string channel, object message);
}

public class Publisher : IPublisher
{
    static Publisher _instance = new Publisher();
    public static Publisher Instance { get { return _instance; } }
    Dictionary<string, List<ISubscriber>> _subscribers = new Dictionary<string, List<ISubscriber>>();

    public void Subscribe(ISubscriber subscriber, string channel)
    {
        if (!Exists(channel))
            _subscribers[channel] = new List<ISubscriber>();
        if (!_subscribers[channel].Contains(subscriber))
            _subscribers[channel].Add(subscriber);
    }

    public void Unsubscribe(ISubscriber subscriber, string channel)
    {
        if (Exists(channel))
            _subscribers[channel].Remove(subscriber);
    }

    public void Publish(string channel, object message)
    {
        if (!Exists(channel)) return;
        foreach (var subscriber in _subscribers[channel])
            subscriber.SubscriptionUpdate(message);
    }

    bool Exists(string channel)
    {
        return _subscribers.ContainsKey(channel);
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ " I presume I need to Unsubscribe in the subscriber's finalizer." If not unsubscribing will prevent the subscriber from being GCed, who would call the finalizer? Maybe you want a Dispose instead? Or Weak Event Pattern? \$\endgroup\$ May 29, 2013 at 11:53

1 Answer 1

2
\$\begingroup\$

Update (2022): If you do want to use an in-process publish-subscribe pattern - consider using a very popular package MediatR. Events are kind of a legacy approach now.

Original answer (2013): Don't think it's a good approach.

  • Singletons are evil by themselves
  • Subscribers are likely to expect a specific type of message on specific channel
  • Magic strings that specify the channel name are also potential source of bugs
  • Incorrect messages or posts to wrong channels are detected at run-time only.
  • Logical dependency between objects still exist, but is hidden.

.NET already has a notion of in-process publish/subscribe, and (not that surprisingly) this functionality is covered by events... All you need to do is to wire up all your entities properly, and that's your actual challenge is.

I would recommend using any of available IoC frameworks for that (I use Autofac at the moment).

  • You will have a strictly-typed communication protocol between publisher and subscriber
  • It will allow you to explicitly declare dependency on a certain external functionality in your classes.
  • It will simplify unit-testing of your classes

Unfortunately you haven't described specific use cases for your pub/sub requirement so it's hard to show this solution on specific example

\$\endgroup\$
3
  • \$\begingroup\$ I did end up using an event, and the design is cleaner and safer as you suggest. However, it's worth pointing out that typical event usage forces filtering to be done by the receiver. An intermediary is nice to filter the events (which is one of the important features of the Publisher above). \$\endgroup\$ May 29, 2013 at 20:09
  • \$\begingroup\$ You don't need to filter events, just create several events and raise only relevant ones... Treat each "channel" (in terms of your solution) as a separate event declaration on one of the interfaces \$\endgroup\$
    – almaz
    May 30, 2013 at 7:40
  • \$\begingroup\$ In the context of this discussion (pub/sub), that may not work. Channel names can be data-driven. \$\endgroup\$ May 31, 2013 at 19:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.