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 IApi2
from 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
.