Ben Burns
Ben Burns

Reputation: 15226

Why can I not cast to a generic interface from a type with a generic argument which is a subtype of the generic argument in my interface?

Given the following interface:

public interface IController<T> where T: Request {
    Response HandleRequest(T request);
}

And the following Request class:

public BlergRequest : Request {
}

And the following controller implementation:

public BlergController : IController<BlergRequest> {
    public Response HandleRequest(BlergRequest request) {
        return new Response("Here's your blergs!");
    }
}

Why is the following an InvalidCastException?

IController<Request> controller = (IController<Request>)new BlergController();

In my "Immediate" window in the debugger, I have the following:

typeof(Request).IsAssignableFrom(typeof(BlergRequest))
true

typeof(IController<Request>).IsAssignableFrom(typeof(BlergController))
false

What gives? I thought this was the whole point of generic constraints?

Upvotes: 1

Views: 286

Answers (2)

Pieter Geerkens
Pieter Geerkens

Reputation: 11893

To follow up on @Asad's comment; try this:

public class Request { }
public class Response {
  public Response(string text) {}
}

public interface IController<out T> where T: Request {
    Response HandleRequest(T request);
}

public class BlergRequest : Request { }

public class BlergController : IController<BlergRequest> {
    public Response HandleRequest(BlergRequest request) {
        return new Response("Here's your blergs!");
    }

    public void Test() {
      IController<Request> controller = new BlergController();
    }
}

However, the argument to IController.HandleRequest is (probably) not covariant as written. Extending the use of interfaces however, makes this compile cleanly without a specification of variance, which may meet your needs:

public interface IRequest { }

public class Request : IRequest { }
public class Response {
  public Response(string text) {}
}

public interface IController<T> where T: IRequest {
    Response HandleRequest(T request);
}

public class BlergRequest : IRequest { }

public class BlergController : IController<IRequest> {
    public Response HandleRequest(BlergRequest request) {
        return new Response("Here's your blergs!");
    }

    public void Test() {
      IController<IRequest> controller = new BlergController();
    }
}

Upvotes: 2

user1726343
user1726343

Reputation:

The fact that you're not allowed to do this is perfectly logical from a type safety perspective. If an IController<BlergRequest> accepts BlergRequests in various methods (in your example, HandleRequest is one such method), it stands to reason that those methods might invoke members that are only available on a BlergRequest, and not on a Request. Thus you can't assign (or cast) an IController<BlergRequest> to an IController<Request>.

If on the other hand your interface was only returning BlergRequests, and never consumed them, then it could return a BlergRequest to any consuming code that required a Request. In such a case, your interface would be covariant in T, and you could mark the type parameter T with the covariant out modifier. You could then assign IController<BlergRequest> wherever an IController<Request> is needed.

Upvotes: 5

Related Questions