I'm working with WebAPI, my API needs to call an external API to get data back for processing.
I designed a BaseResponse class as below:
public interface IResponseData
{
}
public class BaseResponse<T> where T : IResponseData
{
public int ResponseId { get; set; }
public T? Data { get; set; }
}
Since Data must be some kind of response data, the T parameter must implement IResponseData.
But then I realize I can just do this instead and get rid of the entire generic part.
public interface IResponseData
{
}
public class BaseResponse
{
public int ResponseId { get; set; }
public IResponseData? Data { get; set; }
}
What is the point of using where T : IResponseData here? Are there any cases the first example is better than the second one, and vice versa?
For more context (this is quite long text), currently, I have 2 types of response, SingleResponse and MultipleResponse, both of which implement IResponseData.
I was trying to do this:
BaseResponse<IResponseData> baseResponse = new BaseResponse<SingleResponse>();
And I get an error saying that I can not convert SingleResponse to IResponseData, despite the fact that SingleResponse implements IResponseData.
I did some research about covariant, but still don't understand how to make the code above work. Can someone show me how to make this work? (more detail context info below.)
The reason why I write the code above is because:
- I have a method that processes response based on the input parameter count. If count is 1,
SingleRequestandSingleResponse, if count > 1,MultipleRequestandMultipleResponse. - I have to prepare a request, call API, validate response, and process response data for either case.
- Request classes don't have anything in common. Only responses have, which is why I create base class
BaseResponse.
So before, you can imaging the code to be like this:
if (input.Count == 1)
{
SingleRequest singleRequest = new SingleRequest();
BaseResponse<SingleResponse> singleResponse = await getSingleAPI();
Validate(singleResponse);
Process(singleResponse);
}
else if (input.Count > 1)
{
MultipleRequest multipleRequest = new MultipleRequest();
BaseResponse<MultipleResponse> multipleResponse = await getMultipleAPI();
Validate(multipleResponse);
Process(multipleResponse);
}
As you can see, only the request is different. The follow-up process is the same for both cases. I don't want code duplication. So, I want to create a BaseResponse<T> with T implements IResponseData.
Now, I can do this outside the if else
BaseResponse<MultipleResponse> response;
if (input.Count == 1)
{
SingleRequest singleRequest = new SingleRequest();
response = await getSingleAPI();
}
else if (input.Count > 1)
{
MultipleRequest multipleRequest = new MultipleRequest();
response = await getMultipleAPI();
}
Validate(response)
Process(response)
But response = await getSingleAPI(); and response = await getMultipleAPI(); will not compile since getSingleAPI() returns BaseResponse<SingleResponse>() and getMultipleAPI() returns BaseResponse<MultipleResponse>().
They produce the same error as BaseResponse<IResponseData> baseResponse = new BaseResponse<SingleResponse>();.
Here's one reason: Code using your
BaseResponsemight want to use a class that implementsIResponseDatawhich contains items that are not part ofIResponseData.For example, given:
You might have code like this:
But if you change the
BaseResponseclass definition tothen the code will no longer compile.
If you can limit the usage of the type parameter to just the return type of items in the interface (rather than argument types for items in the interface) then you can allow some conversions by using the
outkeyword (generic modifier).An example will make this clearer:
You would invent a new
IBaseResponse<T>interface, and declare its type parameter asoutlike so:Note that this interface does not have a setter for
Data. If it did, the code would not compile because you're not allowed to use anoutgeneric type as an input.Anyway, after creating that interface your class can implement it like so:
Assuming the previous definition of
MyResponseData, now the following code will compile OK:It's not quite what you want, because
IBaseResponse<IResponseData>.Datadoes not have a setter, of course.