如何将 Func<T, object> 转换为 Func<Exception, object>

How to cast Func<T, object> to Func<Exception, object>

我想创建一个字典来存储异常和该异常的格式化程序。

Dictionary<Type, Func<Exception, ApiErrorResponse>>

我可以像这样直接将它们添加到字典中

Dictionary<Type, Func<Exception, ApiErrorResponse>> _exceptionFormatters =
            new Dictionary<Type, Func<Exception, ApiErrorResponse>>
            {
                {
                    typeof(ValidationException),
                    ex =>
                    {
                        var e = ex as ValidationException;
                        return new ApiErrorResponse(HttpStatusCode.UnprocessableEntity, e.Errors);
                    }
                },
                {
                    typeof(NotFoundException),
                    ex =>
                    {
                        var e = ex as NotFoundException;
                        return new ApiErrorResponse(HttpStatusCode.NotFound, e.Message);
                    }
                }
            };

但我想用通用的 AddExceptionFormatter 方法

public static void AddExceptionFormatter<T>(Func<T, ApiErrorResponse> Value) where T : Exception
{
    if (_exceptionFormatters.ContainsKey(typeof(T)))
        _exceptionFormatters[typeof(T)] = (Func<Exception, ApiErrorResponse>)Value;
    else
        _exceptionFormatters.Add(typeof(T), (Func<Exception, ApiErrorResponse>)Value);
}

下面是我如何调用它。

AddExceptionFormatter<NotFoundException>(ex =>
            {
                return new ApiErrorResponse(
                    HttpStatusCode.NotFoundException,
                    nameof(NotFoundException),
                    ex.Message);
            });

但不幸的是我越来越 Unable to cast object of type 'System.Func`2[NotFoundException,ApiErrorResponse]' to type 'System.Func`2[System.Exception,ApiErrorResponse]'.

有什么想法可以如何施放我的Func<,>

我认为这样的转换是不可能的,因为就编译器而言,这两个函数声明完全无关。

您可以尝试将 AddExceptionFormatter 的声明更改为

public static void AddExceptionFormatter<T>(Func<Exception, ApiErrorResponse> Value) where T : Exception

那就不用投什么了

您也许还可以使用接口来调用格式化函数和通用实现。尽管这更复杂并且可能没有那么高效。我没有尝试编译下面的代码,所以更像是一个指南。

interface IExceptionFormatter
{
    ApiErrorResponse Format(Exception)
} 

class ExceptionFormatter<T> : IExceptionFormatter
{
    private Func<T, ApiErrorResponse> _formatDelegate;
    public ExceptionFormatter(Func<T, ApiErrorResponse> formatDelegate)
    {
        _formatDelegate = formatDelegate;
    }

    public ApiErrorResponse Format(Exception ex)
    {
        return _formatDelegate(ex);
    }
}

public static void AddExceptionFormatter<T>(Func<T, ApiErrorResponse> Value) where T : Exception
{
    Type templateType= typeof(ExceptionFormatter<>);
    Type[] args = {typeof(T)};
    Type desiredType = templateType.MakeGenericType(args);
    if (_exceptionFormatters.ContainsKey(typeof(T)))
        _exceptionFormatters[typeof(T)] = Activator.CreateInstance(desiredType, Value);
    else
        _exceptionFormatters.Add(typeof(T), Activator.CreateInstance(desiredType, Value);
}

然后你的字典可以包含各种异常类型的具体实现。

字典 _exceptionFormatters

字典正在存储“采用任何 Exceptions 的函数”,但您为字典提供了一个“采用特定 ExceptionT 的函数”。显然,“采用特定 ExceptionT 的函数”不是一种“采用任何 Exception 的函数”。

要了解为什么这会中断,假设:

AddExceptionFormatter<NotFoundException>(x => x.Foo);

其中 FooNotFoundException 拥有的 属性,类型为 ApiErrorResponse

现在如果我做这件淘气的事:

_exceptionFormatters[typeof(NotFoundException)](new Exception())

显然,这通过了类型检查,因为我可以将 Exception 传递给 Func<Exception, ApiErrorResponse>,但 Exception 实际上没有 Foo 属性!这就是为什么你不能投射。

这里的根本问题是你不能向编译器保证“如果 typeof(X) 是字典的键之一,_exceptionFormatters[typeof(X)] 将是 Func<X, ApiErrorResponse> 类型”。 C# 的类型系统不够强大,无法支持它。

您需要决定当有人做出上述顽皮行为时您希望发生什么。假设你想抛出异常,你可以使用 lambda:

public static void AddExceptionFormatter<T>(Func<T, ApiErrorResponse> Value) where T : Exception
{
    _exceptionFormatters[typeof(T)] = x => Value((T)x);
}

这将导致抛出 InvalidCastException。您当然可以在 lambda 中做一些更花哨的事情:

x => {
    if (x is T t) {
        return Value(t);
    } else {
        throw new YourCustomException("some custom message");
    }
}