Home > front end >  How to specify generic types at runtime?
How to specify generic types at runtime?

Time:09-17

I have a piece of code like this:

  var senders = new List<MessageSenderBase<object>>();
  senders.Add(new MessageSender1<MessageType1>());

where MessageSender1<MessageType1> is derived from MessageSenderBase<T> and MessageType1 is another class I defined.

Now the 2nd line has error which says CS1503 Argument 1: cannot convert from 'Demo.MessageSender.MessageSender1<Demo.MessageSender.MessageType1>' to 'Demo.MessageSender.MessageSenderBase<object>'

How can I solve this issue?

My design is that MessageSenderBase<T> will sit in my library, and at the API level the user can derive their own sender class and specify their own type of message they want to send. I thought this would work since object is the base class of MessageType1 and MessageSenderBase<> is the base class of MessageSender1<>.

Please help, thanks!

Edit - adding MessageSenderBase

    public class MessageSender1<MessageType1> : MessageSenderBase<MessageType1>
    {
        public override string Topic => "topic1";

        public async override Task SendAsync()
        {
            //...
        }
    }

    public abstract class MessageSenderBase<T>
    {
        public abstract string Topic { get;}
        public T Deserialize(string json)
        {
            return JsonConvert.DeserializeObject<T>(json);
        }
        public abstract Task SendAsync();
    }

CodePudding user response:

One way to work around this is to add a new covariant interface that has the "shape" of MessageSenderBase<T>:

public interface IMessageSender<out T> {
    string Topic { get; }
    T Deserialize(string json);
    Task SendAsync();
}
public abstract class MessageSenderBase<T>: IMessageSender<T>
{
    public abstract string Topic { get;}
    public T Deserialize(string json)
    {
        return JsonConvert.DeserializeObject<T>(json);
    }
    public abstract Task SendAsync();
}

If you now make your list to be of type List<IMessageSender<object>>, you can put AnyMessageSender<AnythingReferenceType> in it.

var senders = new List<IMessageSender<object>>();
senders.Add(new MessageSender1<MessageType1>());

Note that if MessageSenderBase has a method that takes a T as parameter (or more generally, has a T in an "input position"), you should not include that in IMessageSender, because if you did, it is not safe to convert from MessageSender1<MessageType1> to IMessageSender<object> anymore. Imagine if you had:

public interface IMessageSender<out T> {
    ...
    void Foo(T someT); // suppose this is implemented in MessageSender1
}

And you would be able to do:

var senders = new List<IMessageSender<object>>();
senders.Add(new MessageSender1<MessageType1>());
senders[0].Foo("abc"); // let's try giving a string to Foo

But senders[0] is a MessageSender1<MessageType1> object. MessageSender1<MessageType1>.Foo only accepts MessageType1 objects, not string!

  • Related