Search code examples
c#genericsreflectionconcrete

C# Resolve Concrete type from Generic Interface


I have the following scenario:

  1. I have several derived exceptions classes that implements a base exception
    //Base exception type
    public class SaberpsicologiaException : Exception
    {
    }

    //One of the derived exception class
    public class MovedPermanentlyException : SaberpsicologiaException
    {
        public string CannonicalUri { get; private set; }

        public MovedPermanentlyException(string cannonicalUri) 
            : base($"Moved permanently to {cannonicalUri}")
        {
            this.CannonicalUri = cannonicalUri;
        }
    } 

  1. For each exception class I want to implement an exceptionHandler that will return an ActionResult, which will implement a common interface:
    interface ISaberpsicologiaExceptionHandler<T>
        where T : SaberpsicologiaException
    {
        ActionResult Result(T exception);
    }

    public class MovedPermanentlyExceptionHandler 
        : ISaberpsicologiaExceptionHandler<MovedPermanentlyException>
    {
        public ActionResult Result(MovedPermanentlyException exception)
        {
            var redirectResult = new RedirectResult(exception.CannonicalUri);
            redirectResult.Permanent = true;

            return redirectResult;
        }
    }

  1. When I catch an exception derived from SaberpsicologiaException I want the appropiate handler to run:
    public class ExceptionHandlerFilter : ExceptionFilterAttribute
    {
        public override void OnException(ExceptionContext context)
        {
            base.OnException(context);

            HandleResponseCodeByExceptionType(context);
        }

        private void HandleResponseCodeByExceptionType(ExceptionContext context)
        {
            var exception = context.Exception;

            if (!CanHandle(exception))
            {
                return;
            }

            var mapping = new Dictionary<Type, Type>
            {
                { typeof(MovedPermanentlyException),  typeof(MovedPermanentlyExceptionHandler) }
            };

            var handlerType = mapping[exception.GetType()];
            var handler = Activator.CreateInstance(handlerType);

            handler.Result(exception); //<- compilation error 
            //handler is type "object" and not MovedPermanentlyExceptionHandler
        }
    }

I tried to resolve it with the Activator (Reflection), but I get to the problem of not really having and object of type ISaberpsicologiaExceptionHandler< [runtime exceptiontype] > so I can't have use the type properly.

In summary the problem is that I have an exception type and I want to get the ISaberpsicologiaExceptionHandler for that exception type, I guess I could use more reflection to just execute the 'Result' method, but I would like to do it a little bit more ellegant.


Solution

  • You didn't show the full context of your class that implements ISaberpsicologiaExceptionHandler<T>. But just from the definition of this interface I would say it doesn't need to be a generic interface. A few possible solutions:

    Solution 1

    Make the method generic:

    interface ISaberpsicologiaExceptionHandler        
    {
      ActionResult Result<TException>(TException exception) where TException : SaberpsicologiaException;
    }
    
    public class MovedPermanentlyExceptionHandler : ISaberpsicologiaExceptionHandler
    {
      public ActionResult Result<TException>(TException exception) where TException : SaberpsicologiaException
      {
        if (exception is MovedPermanentlyException movedPermanentlyException)
        {
          var redirectResult = new RedirectResult(movedPermanentlyException.CannonicalUri);
          redirectResult.Permanent = true;
    
          return redirectResult;
         }
    
         throw new InvalidArgumentException("Exception type not supported", nameof(exception));
       }
    }
    

    Usage:
    To access ISaberpsicologiaExceptionHandler.Result just cast to the non-generic base interface ISaberpsicologiaExceptionHandler no matter the implementing type

    catch (MovedPermanentlyException exception)
    {
      var handler = Activator.CreateInstance(handlerType) as ISaberpsicologiaExceptionHandler;
      handler.Result(exception);
    }
    

    Solution 2

    Use specialized interfaces:

    // General interface
    interface ISaberpsicologiaExceptionHandler
    {
      ActionResult Result(Exception exception);
    }
    
    // Specialized interface
    interface IMovedPermanentlyExceptionHandler : ISaberpsicologiaExceptionHandler
    {
      ActionResult Result(MovedPermanentlyException exception);
    }    
    
    public class MovedPermanentlyExceptionHandler : IMovedPermanentlyExceptionHandler
    {
      public ActionResult Result(MovedPermanentlyException exception)
      {
        var redirectResult = new RedirectResult(exception.CannonicalUri);
        redirectResult.Permanent = true;
    
        return redirectResult;
      }
    
      #region Implementation of ISaberpsicologiaExceptionHandler
    
      // Explicit interface implementation
      ActionResult ISaberpsicologiaExceptionHandler.Result(Exception exception)
      {
        if (exception is MovedPermanentlyException movedPermanentlyException)
        {
          return Result(movedPermanentlyException);
        }
    
        throw new InvalidArgumentException("Exception type not supported", nameof(exception));
      }    
      #endregion
    }
    

    Usage:
    To access ISaberpsicologiaExceptionHandler.Result just cast to the non-generic less specialized base interface ISaberpsicologiaExceptionHandler no matter the implementing type.

    catch (MovedPermanentlyException exception)
    {
      var handler = Activator.CreateInstance(handlerType) as ISaberpsicologiaExceptionHandler;
      handler.Result(exception);
    }
    

    Solution 3

    Use reflection:

    interface ISaberpsicologiaExceptionHandler        
    {
      ActionResult Result<TException>(TException exception) where TException : SaberpsicologiaException;
    }    
    
    public class MovedPermanentlyExceptionHandler : ISaberpsicologiaExceptionHandler
    {
      public ActionResult Result<TException>(TException exception) where TException : SaberpsicologiaException
      {
        if (exception is MovedPermanentlyException movedPermanentlyException)
        {
          var redirectResult = new RedirectResult(movedPermanentlyException.CannonicalUri);
          redirectResult.Permanent = true;
    
          return redirectResult;
         }
    
         throw new InvalidArgumentException("Exception type not supported", nameof(exception));
       }
    }
    

    Usage:
    To access ISaberpsicologiaExceptionHandler.Result just cast to the non-generic base interface ISaberpsicologiaExceptionHandler no matter the implementing type

    catch (MovedPermanentlyException exception)
    {
      var handler = Activator.CreateInstance(handlerType) as ISaberpsicologiaExceptionHandler;
      MethodInfo reflectedMethod = handlerType.GetMethod("Result");
      MethodInfo genericMethod = reflectedMethod.MakeGenericMethod(exception.GetType());
      object[] args = {exception};
      genericMethod.Invoke(this, args);
    }
    

    Solution 4

    Recommended solution.

    Use the proper concrete implementation on invocation:

    I don't know the concept of your exception handler. But since you always know which specific exception you want to catch you can create the proper instance (using a factory at this point is also an option):

    try      
    {
      // Do something that can throw a MovedPermanentlyException
    }
    catch (MovedPermanentlyException e)
    {
      var movedPermanentlyExceptionHandler = new MovedPermanentlyExceptionHandler();
      movedPermanentlyExceptionHandler.Result(e);
    }
    catch (SomeOtherException e)
    {
      var someOtherExceptionHandler = new SomeOtherExceptionHandler();
      someOtherExceptionHandler.Result(e);
    }
    

    There are more solutions so I just take a break. It just boils down to avoid code that uses unknown generic types where members of this unknown type are referenced. I argue that this is always possible and just a question of good design.