C# 从泛型接口解析具体类型

C# Resolve Concrete type from Generic Interface

我有以下场景:

  1. 我有几个派生异常 classes 实现了一个基本异常
    //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. 对于每个异常 class 我想实现一个异常处理程序,它将 return 一个 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
        }
    }

我试图用激活器(反射)解决它,但我遇到了一个问题,即没有真正拥有类型为 ISAberpsicologiaExceptionHandler< [运行time exceptiontype] > 的对象,所以我无法使用类型正确。

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

您最好使用 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
            }
        }

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

您没有显示实现 ISaberpsicologiaExceptionHandler<T> 的 class 的完整上下文。但是从这个接口的定义来看,我会说它不需要是一个通用接口。 一些可能的解决方案:

解决方案 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);
}

解法比较多,先休息一下。它只是归结为避免使用未知泛型类型的代码,其中引用了这种未知类型的成员。我认为这总是可能的,只是一个良好设计的问题。

我采用了更通用的方法,使用 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>();
    }
}