views:

234

answers:

2

Hi,

I have started using of generics in Delphi 2010 but I have a problem when compiling this piece of code:

TThreadBase = class( TThread )
...
end;

TThreadBaseList<T: TThreadBase> = class( TObjectList<T> )
...
end;

TDataProviderThread = class( TThreadBase )
...
end;

TDataCore = class( TInterfacedObject, IDataCore )
private
  FProviders: TThreadBaseList<TDataProviderThread>;
...
end;

Then I have some nested procedure:

procedure MakeAllThreadsActive(aThreads: TThreadBaseList<TThreadBase>);
begin
...
end;

And finally I want to call this nested procedure in the code of TDataCore class:

MakeAllThreadsActive(FProviders);

But compiler does not want to compile it and it says ('<>' brackets are replaced by '()'):

[DCC Error] LSCore.pas(494): E2010 Incompatible types: 'TThreadBaseList(TThreadBase)' and 'TThreadBaseList(TDataProviderThread)'

I do not understand it although TDataProviderThread is descendant of TThreadBase.

I had to fix it by hard typecasting:

MakeAllThreadsActive(TThreadBaseList<TThreadBase>(FProviders));

Does anybody know why the compiler says this error?

+4  A: 

The type

TList <TBase>

is not the parent type of

TList <TChild>

Generics can't be used that way.

Smasher
+14  A: 

TDataProviderThread is a descendant of TThreadBase, but TThreadBaseList<TDataProviderThread> is not a descendant of TThreadBaseList<TThreadBase>. That's not inheritance, it's called covariance, and though it seems intuitively like the same thing, it isn't and it has to be supported separately. At the moment, Delphi doesn't support it, though hopefully it will in a future release.

Here's the basic reason for the covariance problem: If the function you pass it to is expecting a list of TThreadBase objects, and you pass it a list of TDataProviderThread objects, there's nothing to keep it from calling .Add and sticking some other TThreadBase object into the list that's not a TDataProviderThread, and now you've got all sorts of ugly problems. You need special tricks from the compiler to make sure this can't happen, otherwise you lose your type safety.

EDIT: Here's a possible solution for you: Make MakeAllThreadsActive into a generic method, like this:

procedure MakeAllThreadsActive<T: TThreadBase>(aThreads: TThreadBaseList<T>);

Or you could do what Uwe Raabe suggested. Either one will work.

Mason Wheeler
+1 good explanation, even though I know the term *covariance* primarily from methods.
Smasher
If you're only ever reading from the object, then it's a **source** and covariance is OK. If you're writing new values instead, then you have a **sink** and you want contravariance. If it's both a source and a sink (which we must assume TObjectList<T> is, if we have no intrinsic knowledge of what any of its methods do), then you can't have any variance at all. Imbuing the compiler with that concept goes well beyond "special tricks."
Rob Kennedy
The special tricks I'm talking about involve adding support for covariance and contravariance to the compiler, and then marking individual methods of TObjectList<T> as safe for covariance or contravariance in such a way that the compiler can verify it.
Mason Wheeler