Okay, let me set the scene: We have a function used within our code that takes a function and does some logging around it and then returns the result. It looks a little something like this.
TResponse LoggedApiCall<TResponse>(Func<BaseRequest, BaseResponse> apiCall, ...)
where TResponse : BaseResponse;
In use with this I have the four following objects
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 {
...
}
}
So, with these I am trying mock LoggedApiCall (using Moq) in order to support some Unit Tests. I am writing a generic method that allows us to pass in a function that meets the base type constraints and a response that also type matches to create a common method to perform .Setup() on the Mock.
It looks like this:
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);
}
}
The reason I am attempting to cast the function is that if I do not, I get the intellisense error
Argument type 'System.Func<TRequest, TResponse>' is not assignable to
parameter type 'System.Func<Name.Space.Base.BaseRequest, Name.Space.Base.BaseResponse>'
This, I find a little bemusing as TRequest and TResponse are constrained as by BaseRequest and BaseResponse respectively but if work around I must, I shall. However, when performing the cast
var baseFunction = function as Func<BaseRequest, BaseResponse>
it resolves as null. This, I also find bemusing due to the afore mentioned constraints on the parameter passed into SetupLoggedApiCall. I did some further digging whilst debugging the code and got the following:
function is Func<TRequest, TResponse> | true
function is Func<TRequest, BaseResponse> | true
function is Func<BaseRequest, BaseResponse> | false
As this shows, TResponse continues to satisfy BaseResponse and can be cast to it with no issues. However, as soon as we try and move from TRequest to BaseRequest it fails. Just to make sure I wasn't getting into a situation where imported any wrong types or anything I followed this us with:
typeof(TRequest).BaseType == typeof(BaseRequest) | true
So, can anyone tell me: Given that everything points to TRequest being a BaseRequest this cast fails on the matter of TRequest?
We're going to begin stripping this down and isolating the problem in a new code project (in reality, our code is not quite as simple as it is below, I simplified it to it's core) and see at what point it fails and we'll update if we find anything but any insight would be appreciated.
Update 1
Upon following from the suggestion @EugenePodskal I updated the definition of LoggedApiCall to read
TResponse LoggedApiCall<TRequest, TResponse>(Func<TRequest, TResponse> apiCall, ...)
where TRequest : BaseRequest where TResponse : BaseResponse
This made SetupLoggedApiCall happy, at least at compile time, in that what was being passed in would be valid however, the call to the mocked service still returns null. I dug back into the proxy object and down to the interceptor for this call and I discovered this:
IService service => service.LoggedApiCall<Request, Response>(, /*other params*/)
That is not a typo. The first parameter simply missing from the interceptor. I guess this shifts the question more to being about Mock than Func but seeing as that the interceptor is a lamba expression anyone able to shed light on what could cause that paramater to simply be missing?
The topic you need to look at is Covariance and Contravariance in Generics
http://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx
i.e. "In the .NET Framework 4, the Func generic delegates, such as Func, have covariant return types and contravariant parameter types."
It is logical as well, if you think about it..
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);
Hence for function parameters, you do not want to allow up cast to base type.
On the other hand, for return values, if the function was originally declared to return instance of AppleProduct
, it is 100% (type) safe to say it returns an instance of FuitProduct
(base class for AppleProduct
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With