Search code examples
c#genericscastinginversion-of-controlservice-locator

Runtime Casting of Generic Interface


I have a command processing routine that needs to get a Processor object from my IOC container and call the ProcessCommand on it, the processors are concrete object implementing a generic interface:

public interface ICommandProcessor<in T> where T : Command
{        
    Error ProcessCommand(T command);
}

the problem I have is that when I try to retreive the processor I only have a reference to the command object via the base class (Command) so I do not know the type of T (except at runtime)

I have introduced a second non-generic interface

public interface ICommandProcessorGeneric
{

}

public interface ICommandProcessor<in T>:ICommandProcessorGeneric where T : Command
{        
    Error ProcessCommand(T command);
}

and can retreive the processor from the IOC container as follows:

 protected virtual CommandProcessingResult RecognizeAndProcessCommand<T>(T command) where T : Command
 {
    ICommandProcessor<T> commandProcessor;

    try
    {
        Debug.WriteLine(String.Format( "---- Looking for processor for type {0}", command.GetType().Name));
        var g = ServiceLocator.GetInstance<ICommandProcessorGeneric>(command.GetType().Name);                
        commandProcessor = g as ICommandProcessor<T>;  //returns null!!
        Debug.WriteLine(String.Format("---- Got Processor {0}", g.GetType().Name));
    }
    catch (Exception ex)
    {
        //handle the exception
    }
    return commandProcessor.ProcessCommand(command);
}

the problem is that commandProcessor is then null as T is Command, whereas at runtime I know that T is a derived type. the object g returned from the IOC container is the correct processorm however I cannot see how I can cast g to a type that will allow me to access the ProcessCommand method.

I should also point out that the call into this method has no way of determining the type of the command at compile time as the command is retreived from a database by reference only to at ID field and deserialised.


Solution

  • I've written a blog post on this very issue.

    So basically what I've done in the past is to create an abstract base class for your Command Processor along with a generic one:

    public abstract class CommandProcessor
    {
        public abstract Error ProcessCommand(Command command);
    }
    
    public abstract class CommandProcessor<TCommand> where TCommand : Command
    {
        public override Error ProcessCommand(Command command)
        {
            if (command is TCommand == false)
                throw new ArgumentException("command should be of type TCommand");
    
            var cast = (TCommand)command;
    
            return this.ProcessCommand(cast);
        }
    
        public abstract Error ProcessCommand(TCommand command);
    }
    

    Now you don't need to do anything special dependant of what type of Command you are using:

    protected virtual CommandProcessingResult RecogniseAndProccessCommand<T>
        (T command) where T : Command
    {
        var commandProcessor = (CommandProcessor)ServiceLocator.GetInstance<ICommandProcessorGeneric>(typeof(T).Name);
    
        return commandProcessor.ProcessCommand(command);
    }