6

In IMyMessage.cs

public interface IMyMessage
{
}

In IMyMessageReceiver.cs

public interface IMyMessageReceiver<T> where T: IMyMessage
{
    void HandleMessage(T message);
    void Subscribe();
}

In MyMessagePublisher.cs

public static class MyMessagePublisher
{
    private static Dictionary<Type, List<IMyMessageReceiver<IMyMessage>>> _subscribers;

    static MyMessagePublisher
    {
         _subscribers = new Dictionary<Type, List<IMyMessageReceiver<IMyMessage>>>();
    }

    public static function Subscribe<T>(IMyMessageReceiver<T> receiver) where T: IMyMessage
    {
        Type messageType = typeof (T);
        List<IMyMessageReceiver<IMyMessage>> listeners;

        if(!_subscribers.TryGetValue(messageType, out listeners))
        {
             // no list found, so create it
             List<IMyMessageReceiver<T>> newListeners = new List<IMyMessageReceiver<T>>();
             // ERROR HERE: Can't convert List<IMyMessageReceiver<T>> to List<IMyMessageReceiver<IMyMessage>>
             _subscribers.add(messageType, newListeners);

        }

        //  I would then find the right list and add the receiver it to it but haven't got this far
    }
}

So my hope was to use a bunch of 'IMyMessages' and 'IMyMessageReceivers' to pass messages around. I did a hard coded approach earlier but got sick of 100 different publish/subscrive function names so I figured I'd wrap it all nicely in generics.

My problem is that I can't get the code to work when using generics. Even though I specify the Type T will be of IMyMessage, I cannot use T anywhere where IMyMessage is expected. Maybe I'm just used to base/extended classes as it would work fine there. I've tried various approaches from casting, to being really generic, yet I always run in to the same issue.

Zeritor
  • 313
  • 1
  • 2
  • 8
  • 1
    You're trying to use `T` in a covariant way, but input parameters in C# can only be made contravariant – Sean Dec 18 '13 at 13:42
  • @Sean, is there a solution here then or should I just try create a MyMessage/MyMessageReceiver class and extend them instead? I don't really like the sound of that though as it adds some difficulties. – Zeritor Dec 18 '13 at 13:51
  • @Zeritor - I've added an answer below. – Sean Dec 18 '13 at 14:01

4 Answers4

4

OK here's how I can see it working. Since you're trying to use covariance in a way that is not supported, you'll need to avoid using generics in a few spots. But doing so won't lose any type-safety.

Create a non-generic IMessageReceiver interface so that the types that can't use the generic parameter can use this instead:

public interface IMyMessageReceiver
{
    void HandleMessage(IMyMessage message);

    void Subscribe();
}

public interface IMyMessageReceiver<in T> : IMyMessageReceiver
    where T : IMyMessage
{
    void HandleMessage(T message);
}

You can create a base class to simplify things if you'd like:

public abstract class MyMessageReceiverBase<T> : IMyMessageReceiver<T>
    where T : IMyMessage
{
    public abstract void HandleMessage(T message);

    public void HandleMessage(IMyMessage message)
    {
        if (!(message is T))
            throw new InvalidOperationException();
        HandleMessage((T)message);
    }

    public abstract void Subscribe();
}

Then you can change IMyMessageListeners to use the non-generic version, since it doesn't really need the generic type anyways:

public interface IMyMessageListeners
{
    void Add(IMyMessageReceiver receiver);

    // I added this since I think this is how you're going to use it
    void Send(IMyMessage message);
}

The concrete of this class looks like this:

public class MyMessageListeners : IMyMessageListeners
{
    readonly List<IMyMessageReceiver> _list = new List<IMyMessageReceiver>();

    public void Add(IMyMessageReceiver receiver)
    {
        _list.Add(receiver);
    }

    public void Send(IMyMessage message)
    {
        foreach (var listener in _list)
            listener.HandleMessage(message);
    }
}

Then (finally), your static class will look like this:

public static class MyMessagePublisher
{
    static readonly Dictionary<Type, IMyMessageListeners> _subscribers = new Dictionary<Type, IMyMessageListeners>();

    // I added this too, since I think this is how you intend to use it
    public static void Publish<T>(T message) where T : IMyMessage
    {
        Type messageType = typeof(T);
        IMyMessageListeners listeners;

        if (_subscribers.TryGetValue(messageType, out listeners))
            listeners.Send(message);
    }

    public static void Subscribe<T>(IMyMessageReceiver<T> receiver) where T : IMyMessage
    {
        Type messageType = typeof(T);
        IMyMessageListeners listeners;

        if (!_subscribers.TryGetValue(messageType, out listeners))
        {
            // no list found, so create it
            listeners = new MyMessageListeners();
            _subscribers.Add(messageType, listeners);
        }

        listeners.Add(receiver);
    }
}

And you can use your static class like so:

MyMessagePublisher.Subscribe(new FooMessageReceiver());
MyMessagePublisher.Publish(new FooMessage());
Patrick Quirk
  • 23,334
  • 2
  • 57
  • 88
  • Your solution is identical to using List> which would work anyway. Would it work if using IMyMessageListeners insted of IMyMessageListeners ? – Adrien Buet Dec 18 '13 at 14:06
  • Gah, you're right it is identical, and the `Add(receiver)` call doesn't work either way. I'll rethink this but may withdraw my answer if I can't come up with anything. – Patrick Quirk Dec 18 '13 at 14:15
  • What do in/out do in the '' definition? If you don't think this works I will hold off trying it for now. I feel like I'm going about this all wrong. – Zeritor Dec 18 '13 at 14:20
  • `out` makes the type parameter "covariant", and `in` makes it "contravariant". Too much to talk about in a comment, google it or [start here](http://msdn.microsoft.com/en-us/library/ee207183.aspx) – Patrick Quirk Dec 18 '13 at 14:23
  • @Zeritor I've changed my answer to something that works. – Patrick Quirk Dec 18 '13 at 15:12
  • Hi @PatrickQuirk thanks for the answer. I have got further thanks to this. However, I'm trying to allow a class to be able to be a messenger of multiple types but it won't let me use MyMessageReceiverBase twice! C# only allows multiple inheritance using Interfaces D: – Zeritor Dec 18 '13 at 18:06
  • 1
    @Zeritor You could create an interface `IMyMessageReceiver : IMyMessageReceiver` and add the method `HandleMessage(T2 message)`. You'd need to make a similar base class, but that would get you what you want I'd think – Patrick Quirk Dec 18 '13 at 18:45
  • I also seem to be able to do: `class myClass : MyMessageReceiver, IMyMessageReceiver ... IMyMessageReceiver` and then just make sure that `myClass` has definitions for `HandleMessage ... HandleMessage`. I now have it working from end to end, so thank you very much @PatrickQuirk – Zeritor Dec 18 '13 at 18:53
0

Generics do not support covariance. Therfore IMyMessageReceiver T cannot be cast into IMyMessageReceiver IMyMessage even if T implements IMyMessage. That is why you get your error.

I don't think you should use generics in IMyMessageReceiver interface. I am not sure about what you are trying to achieve but maybe something like that would do the trick:

public interface IMyMessageReceiver
{
    void HandleMessage(IMyMessage message);
    void Subscribe();
}

public class MyMessageReceiver<T> : IMyMessageReceiver where T: IMyMessage
{
    void IMyMessageReceiver.HandleMessage(IMyMessage message)
    {
        HandleMessage(message as T);
    }

    public void HandleMessage(T message) {...}
    public void Subscribe() {...}
}
Adrien Buet
  • 191
  • 10
0

Given that you know the types will always be IMyMessageReceiver<T> you have emulate the behaviour by storing everthing as an object and casting:

private static Dictionary<Type, List<object>> _subscribers;


    public static function Subscribe<T>(IMyMessageReceiver<T> receiver) where T: IMyMessage
    {
      Type messageType = typeof (T);
      List<object> listeners;

      if(!_subscribers.TryGetValue(messageType, out listeners))
      {
        // no list found, so create it
        List<object> newListeners = new List<object>();
        newListeners.Add(receiver)
        _subscribers.add(messageType, newListeners);

      }

      var messageReceivers = listeners.Cast<IMyMessageReceiver<T>>();
}

Since you know the list in the dictionary will always be of a given type you can cast them with confidence!

Sean
  • 60,939
  • 11
  • 97
  • 136
0

In this case KeyedByTypeCollection<T> is appropriate:

public interface IMyMessage
{
}

public interface IMyMessageReceiver<T> where T : IMyMessage
{
    void HandleMessage(T message);
    void Subscribe();
}

public static class MyMessagePublisher
{
    private static readonly KeyedByTypeCollection<IList> Subscribers;

    static MyMessagePublisher()
    {
        Subscribers = new KeyedByTypeCollection<IList>();
    }

    public static void Subscribe<T>(IMyMessageReceiver<T> receiver) where T : IMyMessage
    {
        List<IMyMessageReceiver<T>> listeners = Subscribers.Find<List<IMyMessageReceiver<T>>>();

        if (listeners == null)
        {
            listeners = new List<IMyMessageReceiver<T>>();
            Subscribers.Add(listeners);
        }

        // Now you can use the listeners list

        listeners.Add(receiver);
    }
}
lightbricko
  • 2,649
  • 15
  • 21