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



我有以下情况:

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

我试图用激活器(反射(来解决它,但我遇到了没有真正拥有 ISaberpsicologiaExceptionHandler 类型的对象的问题<[运行时异常类型]>所以我不能正确使用该类型。

总之,问题是我有一个异常类型,我想获取该异常类型的 ISaberpsicologiaExceptionHandler,我想我可以使用更多的反射来执行"结果"方法,但我想做得更优雅一点。

您没有显示实现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);
}

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

我使用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>();
}
}

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

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

相关内容

  • 没有找到相关文章

最新更新