对于Func <t,tresult =“”>,其中A扩展T,A不满足T </t,>

时间:2014-10-21 09:42:23

标签: c# generics mocking func

好的,让我设置场景: 我们在代码中使用了一个函数,该函数接受一个函数并对其进行一些日志记录然后返回结果。看起来有点像这样。

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

我试图强制转换函数的原因是,如果我不这样做,我会得到intellisense错误

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>

它解析为null。由于前面提到的对传递给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,但是看到拦截器是一个lamba表达式,任何人都能够阐明可能导致该参数仅仅丢失的原因吗?

1 个答案:

答案 0 :(得分:1)

您需要查看的主题是Covariance and Contravariance in Generics

http://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx

即。 &#34;在.NET Framework 4中,Func泛型委托(如Func)具有协变返回类型和逆变参数类型。&#34;

如果你考虑它也是合乎逻辑的。

Func<Apple, AppleProduct> MakeAppleProduct = new Func....;

// Assume the cast is allowed at runtime and doesn't throw.
Func<Fruit, FruitProduct> MakeFruitProduct = (Func<Fruit, FruitProduct>) MakeAppleProduct;

//Returns an instance of AppleProduct
MakeFruitProduct(appleInstance);

//Orange is also a Fruit, and hence we are allowed to pass it? Should it be allowed?
MakeFruitProduct(orangeInstance);

因此,对于函数参数,您不希望允许向上转换为基类型。

另一方面,对于返回值,如果函数最初被声明为返回AppleProduct的实例,那么它是100%(类型)安全的说它返回一个实例FuitProductAppleProduct的基类)

相关问题