Search code examples
c#dynamiccastingradixderived

Dynamically resolve and cast base to derived type at runtime


How can I cast a base class to a derived on at runtime. What I am trying to do is the following: I need a system which will hold the subscriptions, where a certain type of message, has an assigned Subscriber. When a message is received, it will bi forwarded to this system, which will do a lookup if there are any subscribers to this kind of message and if so, message is forwarded to subscriber. I have an abstract base class called Response, followed by an abstract layers eg. AFResponse : Response, and then there are the actual (concrete) implementation, eg. Response(abstract) : AFResponse(abstract) : AFIncommingMessage(concrete).

When the packet is received, it goes to MessageBuilder, which resolves the message type and build's it, like so:

public static T Build<T>(Packet packet) where T : Response
{
    Type resolvedType;
    if (!dependencyMap.TryGetValue(packet.MessageId, out resolvedType))
    {
        var str = String.Format("Could not resolve message. Message info: CMD0: {0}, CMD1: {1}, MessageID: {2}",
                                packet.Cmd0, packet.Cmd1, packet.MessageId);
        Debug.WriteLine(str);

        throw new ResolutionFailedException(str);
    }

    ConstructorInfo firstConstructor = resolvedType.GetConstructors().First();

    return  (T) firstConstructor.Invoke(new object[] {packet});
}

Then if message is async, it is forwarded to MessageBinder, which holds the subscriptions of message types.

private void OnAsyncResponseReceived(Response response)
{
    messageBinder.Forward(response);
}

And a MessageBinder class is implemented as following:

public class MessageBinder
{
    private class Subscriber<T> : IEquatable<Subscriber<T>> where T : Response
    {
        ...
    }

    private readonly Dictionary<Type, IEnumerable> bindings;

    public MessageBinder()
    {
        this.bindings = new Dictionary<Type, IEnumerable>();
    }

    public void Bind<TResponse>(ushort shortAddress, Action<ZigbeeAsyncResponse<TResponse>> callback)
        where TResponse : Response
    {
        HashSet<Subscriber<TResponse>> subscribers = this.GetSubscribers<TResponse>();
        if (subscribers != null)
        {
            subscribers.Add(new Subscriber<TResponse>(shortAddress, callback));
        }
        else
        {
            var subscriber = new Subscriber<TResponse>(shortAddress, callback);
            this.bindings.Add(typeof(TResponse), new HashSet<Subscriber<TResponse>> { subscriber });
        }
    }

    public void Forward<TResponse>(TResponse response)
        where TResponse : Response
    {
        var subscribers = this.GetSubscribers<TResponse>();
        if (subscribers != null)
        {
            Subscriber<TResponse> subscriber;

            var afResponse = response as AFResponse;
            if (afResponse != null)
            {
                subscriber = subscribers.SingleOrDefault(s => s.ShortAddress == afResponse.ShortAddress);
            }
            else
            {
                subscriber = subscribers.FirstOrDefault();
            }

            if (subscriber != null)
            {
                Debug.WriteLine("Forwarding received async response of type " + response.GetType().Name);
                subscriber.Forward(response);
            }
        }
    }

    private HashSet<Subscriber<TResponse>> GetSubscribers<TResponse>() where TResponse : Response
    {
        IEnumerable subscribers;
        this.bindings.TryGetValue(typeof(TResponse), out subscribers);

        return (HashSet<Subscriber<TResponse>>)subscribers;
    }
}

The problem arises when a method GetSubscribers() of MessageBinder class is called, because the type of TResponse is of type of a base class, which is Response and therefore no subscriber/s are/is found. So what I thought I need to do is somehow pass the actual type of message to Forward method, so that type would be the actual type of response.

I changed the method like so:

private void OnAsyncResponseReceived(Response response)
{
    messageBinder.Forward((dynamic)response);
}

It does work, but somehow I thing there is a better(?) way to do this ... or is this the actual and only solution? Maybe I should change the overall design, I do not know ... I am facing this type of problem the first time and this is what I came up with.

I am open to suggestions, critic, and of course, the best possible solution :) I am curious to, how would you implement this, if it is different and more efficient than my solution. Thank you for any help!


Solution

  • Have you considered routing the request for subcribers through the message itself? It would not necessarily need to be direct - your base class could require child classes to override a property which exposes some kind of utility object. The utility object could be strongly typed to the message type and would be able to make a strongly typed call to get subscribers.

    You could still use the methods you've created (for fetching subscribers) - the utility class would simply have the ability to make a strongly typed call.

    EDIT:

    How about this?

    private void OnAsyncResponseReceived<T>(T response) where T : Response {
      messageBinder.Forward(response);
    }
    

    The problem with your original code is that the response variable was strongly typed. Casting to dynamic "un-strongly-typed" it. By using a generic method, as you do throughout your application, with the added constraint that the generic type must inherit from Response, you effective get the same result but with the dynamic typing built-in. Honestly, though, I think casting to dynamic is rather clever. :)