【发布时间】:2014-10-31 10:39:30
【问题描述】:
好的,让我来设置场景: 我们在代码中使用了一个函数,它接受一个函数并围绕它进行一些日志记录,然后返回结果。它看起来有点像这样。
TResponse LoggedApiCall<TResponse>(Func<BaseRequest, BaseResponse> apiCall, ...)
where TResponse : BaseResponse;
使用这个我有以下四个对象
namespace Name.Space.Base {
public class BaseRequest {
...
}
}
namespace Name.Space.Base {
public class BaseResponse {
...
}
}
namespace Some.Other.Name.Space {
public class Request : BaseRequest {
...
}
}
namespace Name.Space {
public class Response<TPayload> : BaseResponse {
...
}
}
因此,我正在尝试使用这些模拟 LoggedApiCall(使用 Moq)以支持一些单元测试。我正在编写一个通用方法,它允许我们传入一个满足基本类型约束的函数和一个类型匹配的响应,以创建一个通用方法来在 Mock 上执行 .Setup()。
看起来像这样:
protected IReturnsResult<IService> SetupLoggedApiCall<TRequest, TResponse>(
Func<TRequest, TResponse> function,
TResponse response
)
where TRequest : BaseRequest
where TResponse : BaseResponse
{
var baseFunction = function as Func<BaseRequest, BaseResponse>;
return _mockService.Setup(service => service.LoggedApiCall<TResponse>(
baseFunction, /*other parameters *
))
.Returns(response);
}
}
我尝试强制转换函数的原因是,如果我不这样做,我会收到智能感知错误
Argument type 'System.Func<TRequest, TResponse>' is not assignable to
parameter type 'System.Func<Name.Space.Base.BaseRequest, Name.Space.Base.BaseResponse>'
这个,我觉得有点困惑,因为 TRequest 和 TResponse 分别受到 BaseRequest 和 BaseResponse 的约束,但如果我必须解决,我会的。 但是,在执行演员表时
var baseFunction = function as Func<BaseRequest, BaseResponse>
它解析为空。由于上述对传递给 SetupLoggedApiCall 的参数的限制,我也感到困惑。 我在调试代码时做了一些进一步的挖掘,得到了以下结果:
function is Func<TRequest, TResponse> | true
function is Func<TRequest, BaseResponse> | true
function is Func<BaseRequest, BaseResponse> | false
如图所示,TResponse 继续满足 BaseResponse 并且可以毫无问题地转换为它。但是,一旦我们尝试从 TRequest 移动到 BaseRequest,它就会失败。只是为了确保我没有遇到导入任何错误类型或我跟随我们的任何东西的情况:
typeof(TRequest).BaseType == typeof(BaseRequest) | true
那么,谁能告诉我: 鉴于所有内容都表明 TRequest 是 BaseRequest,因此在 TRequest 问题上该强制转换失败?
我们将开始剥离它并在一个新的代码项目中隔离问题(实际上,我们的代码并不像下面那么简单,我将其简化为核心),看看它在什么时候出现失败,如果我们发现任何东西,我们会更新,但任何见解都将不胜感激。
更新 1
根据@EugenePodskal 的建议,我更新了 LoggedApiCall 的定义以阅读
TResponse LoggedApiCall<TRequest, TResponse>(Func<TRequest, TResponse> apiCall, ...)
where TRequest : BaseRequest where TResponse : BaseResponse
这让 SetupLoggedApiCall 很高兴,至少在编译时是这样,因为传入的内容是有效的,但是对模拟服务的调用仍然返回 null。我深入研究了代理对象并深入到了这个调用的拦截器,我发现了这个:
IService service => service.LoggedApiCall<Request, Response>(, /*other params*/)
这不是错字。拦截器中缺少第一个参数。我想这将问题更多地转移到关于 Mock 而不是 Func 但看到拦截器是一个兰巴表达式,任何人都能够阐明什么可能导致该参数丢失?
【问题讨论】:
-
我看不出这是如何重复的 - 链接的问题是对有关
Func差异的一些细节的澄清,而这个问题实际上需要对一般差异进行介绍/解释。上下文是相同的,但问题不是。另一个问题的答案可能对提出这个问题的人没有帮助。 -
@Rawling 当然,如果你想把事情过分简单化。另一个问题及其答案已经假定理解方差的概念,因此该问题的答案在应用于这个问题时是无用的。例如。 “为什么我不能将
Func<Derived, X>转换为Func<Base, X>”不能正确回答为“你的协变和逆变换错了。” -
Func<T, TResult>可以返回比TResult类型更专业(派生)的任何东西,但它不能返回任何更少派生的东西(如基类),因为调用代码在执行时Func<T, TResult>必须 得到TResult或SomeClass: TResult作为回报,否则不能将委托视为有效的Func<T, TResult>- 编译器不能保证调用Func<T, TBase>将返回有效的 TResult 派生值. -
是的,
Funcs 在参数类型上是逆变的,在返回类型上是协变的。