对于Func <T,TResult>,其中A扩展T,A不满足T

Kir*_*ida 9 c# generics mocking func

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

TResponse LoggedApiCall<TResponse>(Func<BaseRequest, BaseResponse> apiCall, ...)
    where TResponse : BaseResponse;
Run Code Online (Sandbox Code Playgroud)

在使用中我有以下四个对象

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 {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,有了这些,我正在尝试模拟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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Argument type 'System.Func<TRequest, TResponse>' is not assignable to
parameter type 'System.Func<Name.Space.Base.BaseRequest, Name.Space.Base.BaseResponse>'
Run Code Online (Sandbox Code Playgroud)

这个,我发现有点令人困惑,因为TRequest和TResponse分别受到BaseRequest和BaseResponse的限制,但如果我必须解决,我会.但是,在进行演员表演时

var baseFunction = function as Func<BaseRequest, BaseResponse>
Run Code Online (Sandbox Code Playgroud)

它解析为null.由于前面提到的对传递给SetupLoggedApiCall的参数的限制,我也觉得很困惑.我在调试代码时做了一些进一步的挖掘,得到了以下内容:

function is Func<TRequest, TResponse>       | true
function is Func<TRequest, BaseResponse>    | true
function is Func<BaseRequest, BaseResponse> | false
Run Code Online (Sandbox Code Playgroud)

如图所示,TResponse继续满足BaseResponse并且可以毫无问题地转换为它.但是,一旦我们尝试从TRequest迁移到BaseRequest,它就会失败.只是为了确保我没有进入导入任何错误类型的情况或我跟随我们的任何事情:

typeof(TRequest).BaseType == typeof(BaseRequest) | true
Run Code Online (Sandbox Code Playgroud)

所以,任何人都可以告诉我:鉴于所有事情都指向TRequest是一个BaseRequest,这个演员在TRequest问题上失败了吗?

我们将开始剥离它并在新的代码项目中隔离问题(实际上,我们的代码并不像下面那么简单,我将它简化为它的核心),看看它失败了什么点,我们如果我们找到任何东西,我会更新,但任何见解都会受到赞赏.

更新1

根据@EugenePodskal的建议,我更新了LoggedApiCall的定义以便阅读

TResponse LoggedApiCall<TRequest, TResponse>(Func<TRequest, TResponse> apiCall, ...)
    where TRequest : BaseRequest where TResponse : BaseResponse
Run Code Online (Sandbox Code Playgroud)

这使得SetupLoggedApiCall满意,至少在编译时,传入的内容是有效的,但是对模拟服务的调用仍然返回null.我挖回到代理对象,然后到这个调用的拦截器,我发现了这个:

IService service => service.LoggedApiCall<Request, Response>(, /*other params*/)
Run Code Online (Sandbox Code Playgroud)

那不是拼写错误.拦截器中缺少第一个参数.我想这会把问题更多地转移到关于Mock而不是Func,但是看到拦截器是一个lamba表达式,任何人都能够阐明可能导致该参数只是丢失的原因吗?

Vik*_*pta 2

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

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

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

如果你仔细想想,这也是符合逻辑的。

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);
Run Code Online (Sandbox Code Playgroud)

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

另一方面,对于返回值,如果函数最初被声明为返回 的实例AppleProduct,则可以 100%(类型)安全地说它返回FuitProduct( 的基类AppleProduct)的实例