【问题标题】:C# Resolve Concrete type from Generic InterfaceC# 从通用接口解析具体类型
【发布时间】:2020-01-22 03:44:24
【问题描述】:

我有以下场景:

  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. 对于每个异常类,我想实现一个异常处理程序,它将返回一个 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 类型的对象的问题,因此我无法正确使用该类型。

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

【问题讨论】:

  • Activator.CreateInstance 返回object。您缺少例如MovedPermanentlyExceptionHandler:var handler = Activator.CreateInstance(handlerType) as MovedPermanentlyExceptionHandler;
  • 我不能使用通用版本,因为我不知道它是哪种类型,在编译时(我知道我什至不需要使用激活器)

标签: c# generics reflection concrete


【解决方案1】:

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

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

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

【讨论】:

  • 我想我会尝试实施您的解决方案 2,因为我真的不想在这个问题上过于依赖反射。我不太喜欢解决方案 4,因为我不想在控制器中添加太多噪音。非常感谢您的回答:)
【解决方案2】:

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

【讨论】:

  • 非常感谢您的回答,我真的很欣赏它,很棒的工作,但是我保留了@BionicCode 的解决方案,因为我不想在这种情况下过于依赖反射。但是,您的解决方案也很棒:)
【解决方案3】:

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

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

【讨论】:

  • 之前有一段关于使用强制转换的文章,但意识到这并不能解决问题,所以我将其删除。您可能会使用dynamic 类,这可能会允许您调用.Result() 方法,但这很危险并且很容易将错误引入您的代码。如果可能的话,我强烈建议采用这种方法
  • 我有一个使用这种方法的运行版本,但是我觉得我并没有真正使用泛型的潜力,这就是我想改进该代码的原因。我想的是能够将对象转换为接口,但这是不可能的,因为接口实现了具体类型。
  • 附带说明:可以使用带反射的参数,.CreateInstance 的重载之一允许您为“实例化”传递参数列表。
  • @Manjar 是的,确实如此,但除非所有构造函数共享相同的参数,否则很难生成该参数列表;开始往下走是一条危险的道路,最好避开。
猜你喜欢
  • 2019-10-15
  • 2013-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多