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?
Func
while this question actually calls for an introduction/explanation of variance in general. The context is the same, but the question is not. The answer to the other question will likely not help the asker of this one. – ArmijoAction<Derived>
toAction<Base>
", and this question is essentially asking "why can't I castFunc<Derived, X>
toFunc<Base, X>
". – CoelenterateFunc<Derived, X>
toFunc<Base, X>
" can't be properly answered with "You've got the covariance and contravariance the wrong way round." – ArmijoFunc<Derived, X>
toFunc<Base, X>
, it's castingFunc<DerivedA, DerivedB>
toFunc<BaseA, BaseB>
where it works for B (as show by the successful is onFunc<DerivedA, DerivedB>
toFunc<DerivedA, BaseB>
. What I don't understand is why it you work for Out but not In. – OmaromaraFunc<T1, T2>
is contravariant inT1
so if you try to assign to a variable of typeFunc<D1, B2>
thenD1
needs to be a subtype ofT1
. In your example, your argument type is a base type of the argument type so it is unsafe. – MenorrhagiaFunc<T1, T2>
T1 is contravariant but T2 is not hence why we can upcast T2 successfully? Meaning this is the nature ofFunc
. – OmaromaraFunc<T, TResult>
can return anything that is more specialized(derived) thanTResult
type, but it can't return anything less derived(like base class), because the calling code, when it executesFunc<T, TResult>
MUST getTResult
orSomeClass: TResult
in return, otherwise the delegate just cannot be considered a validFunc<T, TResult>
- compiler cannot guarantee that call ofFunc<T, TBase>
will return valid TResult derived value. – OrgeatFunc
s are contravariant in their argument types and covariant in their return type. – MenorrhagiaTResponse LoggedApiCall<TResponse, TRequest>(Func<TRequest, TResponse> apiCall, ...) where...
? – Orgeat