C # Разрешить конкретный тип из общего интерфейса - PullRequest
0 голосов
/ 21 сентября 2019

У меня есть следующий сценарий:

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

Для каждого класса исключений я хочу реализовать 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;
        }
    }

Когда я ловлю исключение, полученное из 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 ]

2 голосов
/ 21 сентября 2019

Вы не показали полный контекст вашего класса, который реализует 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 голос
/ 21 сентября 2019

Я выбрал более общий подход, используя 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>();
    }
}
0 голосов
/ 21 сентября 2019

Возможно, вам лучше использовать оператор if...else или switch.Ваш код может выглядеть примерно так:

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

. Это имеет явное преимущество, заключающееся в том, что вы можете явно использовать конструкторы для этих обработчиков вместо того, чтобы пытаться создавать их с отражением.С отражением вы не сможете добавить дополнительные параметры в ваши конструкторы без большой дополнительной работы и изменений в вашем коде.

...