C#从通用接口解析具体类型

时间:2019-09-21 10:10:54

标签: c# generics reflection concrete

我有以下情况:

  1. 我有几个派生的异常类,它们实现了基本异常
    //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. 对于每个异常类,我要实现一个exceptionHandler,该异常处理程序将返回一个ActionResult,它将实现一个公共接口:
    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. 当我捕获到一个从SaberpsicologiaException派生的异常时,我希望运行适当的处​​理程序:
    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
        }
    }

我试图用Activator(Reflection)来解决它,但是我遇到了一个问题:没有真正拥有类型为ISaberpsicologiaExceptionHandler <[runtime exceptiontype]>的对象,所以我不能正确使用该类型。

总而言之,问题是我有一个异常类型,并且我想获取该异常类型的ISaberpsicologiaExceptionHandler,我想我可以使用更多的反射来执行'Result'方法,但是我想这样做一点点优雅。

3 个答案:

答案 0 :(得分:2)

您没有显示实现ISaberpsicologiaExceptionHandler<T>的类的完整上下文。但是仅从该接口的定义来看,我并不一定要是通用接口。 一些可能的解决方案:

解决方案1 ​​

使方法通用:

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));
   }
}

用法:
要访问ISaberpsicologiaExceptionHandler.Result,只需将其强制转换为非通用基本接口ISaberpsicologiaExceptionHandler,无论实现类型是什么

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

解决方案2

使用专用界面:

// 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
}

用法:
要访问ISaberpsicologiaExceptionHandler.Result,只需将其强制转换为非通用的,不太专业的基本接口ISaberpsicologiaExceptionHandler,无论实现类型是什么。

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

解决方案3

使用反射:

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));
   }
}

用法:
要访问ISaberpsicologiaExceptionHandler.Result,只需将其强制转换为非通用基本接口ISaberpsicologiaExceptionHandler,无论实现类型是什么

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);
}

解决方案4

推荐的解决方案。

在调用时使用适当的具体实现:

我不知道您的异常处理程序的概念。但是由于您始终知道要捕获的特定异常,因此可以创建适当的实例(此时也可以使用工厂):

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);
}

还有更多解决方案,所以我休息一下。它只是归结为避免使用未知的泛型类型的代码引用此未知类型的成员。我认为这始终是可能的,只是一个好的设计问题。

答案 1 :(得分:1)

我使用了 System.Linq.Expressions

的通用方法

考虑到异常类型,将构建一个委托来调用所需的函数

 LambdaExpression buildHandlerDelegate(Type exceptionType, Type handlerType) {
    var type = typeof(ISaberpsicologiaExceptionHandler<>);
    var genericType = type.MakeGenericType(exceptionType); //ISaberpsicologiaExceptionHandler<MyException>
    var handle = genericType.GetMethod("Result", new[] { exceptionType });
    var func = typeof(Func<,>);
    var delegateType = func.MakeGenericType(typeof(Exception), typeof(ActionResult));

    //Intension is to create the following expression:
    // Func<Exception, ActionResult> function = 
    // (exception) => (new handler()).Result((MyException)exception);

    // exception =>
    var exception = Expression.Parameter(typeof(Exception), "exception");
    // new handler()
    var newHandler = Expression.New(handlerType);
    // (MyException)exception
    var cast = Expression.Convert(exception, exceptionType);
    // (new handler()).Result((MyException)exception)
    var body = Expression.Call(newHandler, handle, cast);
    //Func<TException, ActionResult> (exception) => 
    //  (new handler()).Result((MyException)exception)
    var expression = Expression.Lambda(delegateType, body, exception);
    return expression;
}

,并且可以与以下过滤器一起使用

//...

var exceptionType = exception.GetType();
var handlerType = mapping[exceptionType]; 

var handler = buildHandlerDelegate(exceptionType, handlerType).Compile();

var result = handler.DynamicInvoke(exception);

context.Result = (IActionResult)result;

//...

这是完整的实现

public class ExceptionHandlerFilter : ExceptionFilterAttribute {
    public override void OnException(ExceptionContext context) {
        base.OnException(context);
        HandleResponseCodeByExceptionType(context);
    }
    static readonly Dictionary<Type, Type> mapping = new Dictionary<Type, Type>
    {
        { typeof(MovedPermanentlyException), typeof(MovedPermanentlyExceptionHandler) }
    };

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

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

        var exceptionType = exception.GetType();
        var handlerType = mapping[exceptionType];

        var handler = buildHandlerDelegate(exceptionType, handlerType).Compile();

        var result = handler.DynamicInvoke(exception);

        context.Result = (IActionResult)result;
    }

    LambdaExpression buildHandlerDelegate(Type exceptionType, Type handlerType) {
        var type = typeof(ISaberpsicologiaExceptionHandler<>);
        var genericType = type.MakeGenericType(exceptionType); //ISaberpsicologiaExceptionHandler<MyException>
        var handle = genericType.GetMethod("Result", new[] { exceptionType });
        var func = typeof(Func<,>);
        var delegateType = func.MakeGenericType(typeof(Exception), typeof(ActionResult));

        //Intension is to create the following expression:
        // Func<Exception, ActionResult> function = 
        // (exception) => (new handler()).Result((MyException)exception);

        // exception =>
        var exception = Expression.Parameter(typeof(Exception), "exception");
        // new handler()
        var newHandler = Expression.New(handlerType);
        // (MyException)exception
        var cast = Expression.Convert(exception, exceptionType);
        // (new handler()).Result((MyException)exception)
        var body = Expression.Call(newHandler, handle, cast);
        //Func<TException, ActionResult> (exception) => 
        //  (new handler()).Result((MyException)exception)
        var expression = Expression.Lambda(delegateType, body, exception);
        return expression;
    }
}

使用以下单元测试来验证预期行为

[TestClass]
public class ExceptionHandlerFilterTests {
    [TestMethod]
    public void Should_Handle_Custom_Exception() {
        //Arrange
        var subject = new ExceptionHandlerFilter();
        var url = "http://example.com";
        var context = new ExceptionContext(Mock.Of<ActionContext>(), new List<IFilterMetadata>()) {
            Exception = new MovedPermanentlyException(url)
        };

        //Act
        subject.OnException(context);

        //Assert
        context.Result.Should()
            .NotBeNull()
            .And.BeOfType<RedirectResult>();
    }
}

答案 2 :(得分:0)

使用if...elseswitch语句可能会更好。您的代码可能看起来像这样

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

            if (!CanHandle(exception)) return;

            var exceptionType = exception.GetType();

            if (exceptionType == typeof(MovedPermanantelyException)) {
                 var handler = new MovePermanentlyExceptionHandler();
                 handler.Result(exception);
            }
            else {
                // chain the rest of your handlers in else if statements with a default else
            }
        }

这具有明显的优势,即允许您为这些处理程序显式使用构造函数,而不是尝试通过反射创建它们。借助反射,如果不进行大量额外的工作和代码修改,您将无法向构造函数添加其他参数。