Home > Net >  Cannot implicitly convert type X to 'T' when the 'where' condition is in the cla
Cannot implicitly convert type X to 'T' when the 'where' condition is in the cla

Time:09-28

Consider this code:

class Model  {}

interface IApi1<T> where T: Model 
{ 
   T Create(T entity); 
}

interface IApi2 
{ 
   T Create<T>(T entity) where T: Model; 
}

class Foo {
    IApi1<Model> api1;
    IApi2 api2;
    
    public T Do1<T>(T d) where T: Model => api1.Create(d); // ERROR
    public T Do2<T>(T d) where T: Model => api2.Create(d); // WORKS
}

Why is the line marked as "ERROR" gives me the "Cannot implicitly convert type 'Model' to 'T'" but the seemingly same code works in the next line? The only difference in the IApi2from IApi1 is that the where condition moved from the class declaration into the method declaration. Why does it make a difference?

I am not looking for a solution; I am merely curious why the behavior.

CodePudding user response:

Let me rename some of the type parameters, so that it is easy to see what we are talking about:

interface IApi1<TApi1> where TApi1: Model 
{ 
   TApi1 Create(TApi1 entity); 
}

interface IApi2 
{ 
   TApi2 Create<Api2T>(TApi2 entity) where TApi2: Model; 
}

class Foo {
    IApi1<Model> api1;
    IApi2 api2;
    
    public TDo1 Do1<TDo1>(TDo1 d) where TDo1: Model => api1.Create(d); // ERROR
    public TDo2 Do2<TDo2>(T d) where TDo2: Model => api2.Create(d); // WORKS
}

When a type parameter is declared on the type, like in the case of IApi1, it is "fixed" (or "determined") at the moment you write the type. That is, the moment you wrote:

 IApi1<Model> api1;

it is decided that api1.Create would take in a Model, and return a Model. The type parameter (TApi1) is substituted with the type argument (Model).

On the other hand, if a type parameter is declared on a method, it is not "fixed" until you call that method. IApi2.Create can take any TApi2 and return that same type TApi2, where TApi2 is a Model. It is only when you call Create, that C# decides what TApi2 is.

Both Do1 and Do2 declares type parameters too, and I've called them TDo1 and TDo2. Note that these are distinct from the type parameters TApi1 and TApi2.

In Do1, you try to return what IApi1.Create returns. This doesn't work, because IApi1.Create returns a Model, but Do1 is declared to return TDo1, which if you recall, will only be determined when the caller calls Do1. The caller of Do1 might as well pass another Model subclass as the type argument for TDo1!

In Do2, returning IApi2.Create is okay, because the actual type that IApi2.Create returns (i.e. TApi2) is not determined until you call it. In this case, it is inferred that IApi2.Create will return TDo2, which, in turn will be determined when someone calls Do2.

  • Related