Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cast concrete type to generic type?

Tags:

c#

generics

I'm trying to compile the following code:

public class BaseRequest<TResponse> where TResponse : BaseResponse {}
public class BaseResponse {}

public class FooRequest : BaseRequest<FooResponse> {}
public class FooResponse : BaseResponse {}

...

public TResponse MakeRequest<TResponse>(BaseRequest<TResponse> request)
     where TResponse : BaseResponse
{
}

I wish I can call MakeRequest(new FooRequest()) and get the returned value as FooResponse. The callee does not have to know about FooRequest and may pass it on to another handler. The signatures worked fine, however I cannot implement the MakeRequest method. If I implement it like:

public TResponse MakeRequest<TResponse>(BaseRequest<TResponse> request)
     where TResponse : BaseResponse
{
    FooRequest fooRequest = request as FooRequest;
    if (fooRequest != null)   // if I can handle the request, handle it
    {
        return new FooResponse(...);   // ***
    }

    BarRequest barRequest = request as BarRequest;
    if (barRequest != null) 
    {
        return new BarResponse(...);  
    }

    else                      // otherwise, pass it on to the next node
    {
        // maybe it will handle a BazRequest, who knows
        return nextNode.MakeRequest(request);
    }
}

But the *** line won't compile because the compiler does not know FooResponse is a TResponse. I know it is because it's specified in FooRequest. Is there any way to work around this without involving nasty Reflection (in which case I'd rather return BaseResponse instead)?

Thanks.

Update: I'm using generics to enforce the return type so the call site knows exactly what to expect. It would be much easier to just return BaseResponse here, but it puts the burden of figuring out the concrete return type to the caller rather than the request handler (which of course knows all about the typing).

like image 468
Todd Li Avatar asked Feb 23 '12 19:02

Todd Li


2 Answers

As I said in my comment, I suspect that you're doing it wrong. This looks like an abuse of generics.

That said, the way you tell the compiler "I know more type information than you do" is by casting.

var response = new FooResponse(...);   
return (TResponse)(object)response;

The cast to object and then to TResponse tells the compiler "I know that there's an identity, unboxing or reference conversion from response to TResponse". If you're wrong, you'll get an exception at runtime.

like image 191
Eric Lippert Avatar answered Nov 05 '22 18:11

Eric Lippert


In my opinion, you should derive your BaseRequest<T> class from a non generic version BaseRequest, and then write your function as:

public BaseResponse MakeRequest(BaseRequest request)

This seems to me like the right way to do it since you are not even referring to the type inside the function.

It seems to me that the generics are only here as syntactic sugar. What you're gaining from writing your function the way you do is to be able to write:

FooResponse r = MakeRequest(new FooRequest(...))

instead of this:

FooResponse r = (FooResponse)MakeRequest(new FooRequest(...))

So the upside is not huge. And the fact that you got confused by your own code to the point that you could not see that the thing that was missing was casting means that the code is probably much less clear that way than the non-generic way.

Oh, and another downside of your method is that you will lose the ability to do:

var requests = new List<BaseRequest> { new FooRequest(), new BarRequest() };
var responses = new List<BaseResponse> ();
foreach(var request in requests)
{
    responses.Add(MakeRequest(request));
}

Or what you can do is to have:

public BaseResponse MakeRequest(BaseRequest request) { /* thing that does the work */ }
public TResponse MakeRequest<TResponse>(BaseRequest<TResponse> request)
{
    // Just for the nice syntax
    return (TResponse)MakeRequest(request);
}

But that looks really convoluted. Anyway I will let you reflect on that

like image 28
edeboursetty Avatar answered Nov 05 '22 19:11

edeboursetty