【问题标题】:How to cast Func<T, object> to Func<Exception, object>如何将 Func<T, object> 转换为 Func<Exception, object>
【发布时间】:2021-05-08 16:44:41
【问题描述】:

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

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&lt;,&gt;吗?

【问题讨论】:

  • 使用TryGetValue 而不是ContainsKey。它对您当前的问题没有帮助,但会更快。

标签: c# .net generics type-conversion


【解决方案1】:

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

您可以尝试将 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

【讨论】:

  • 是的,但是添加格式化程序后,我需要将 Exception 转换为特定的(以访问其他属性)。
  • 我已经用其他选项扩展了我的答案
【解决方案2】:

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

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

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

其中FooNotFoundException 具有的属性,并且是ApiErrorResponse 类型。

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

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

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

这里的根本问题是你不能向编译器保证“如果typeof(X) 是字典的键之一,_exceptionFormatters[typeof(X)] 将是Func&lt;X, ApiErrorResponse&gt; 类型”。 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");
    }
}

【讨论】:

  • _exceptionFormatters 将是私有的,因此没有人可以访问它,添加格式化程序的唯一方法是使用 Add.. 方法:)所以我猜你的第一个解决方案对我来说已经足够了 :) 谢谢!!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多