2012-04-27 4 views
8

Ho un problema con il mio codice, che utilizza tipi generici. Perché il compilatore non sa che l'elenco passato (Result) è un TObjectList<TItem> (TItem è il tipo per T in TItems)?Perché non posso passare una TObjectList <S: T> a una funzione che si aspetta una TObjectList <T>?

Interfaccia:

type 
    TItem = class 
end; 

type 
    IItemsLoader = interface 
    procedure LoadAll(AList : TObjectList<TItem>); 
end; 

type 
    TItemsLoader = class(TInterfacedObject, IItemsLoader) 
public 
    procedure LoadAll(AList : TObjectList<TItem>); 
end; 

type 
    IItems<T : TItem> = interface 
    function LoadAll : TObjectList<T>; 
end; 

type 
    TItems<T : TItem> = class(TInterfacedObject, IItems<T>) 
    private 
    FItemsLoader : TItemsLoader; 
    public 
    constructor Create; 
    destructor Destroy; override; 
    function LoadAll : TObjectList<T>; 
end; 

Implementazione:

procedure TItemsLoader.LoadAll(AList: TObjectList<TItem>); 
begin 
    /// some stuff with AList 
end; 

{ TItems<T> } 

constructor TItems<T>.Create; 
begin 
    FItemsLoader := TItemsLoader.Create; 
end; 

destructor TItems<T>.Destroy; 
begin 
    FItemsLoader.Free; 
    inherited; 
end; 

function TItems<T>.LoadAll: TObjectList<T>; 
begin 
    Result := TObjectList<T>.Create(); 

    /// Error here 
    /// FItemsLoader.LoadAll(Result); 
end; 

risposta

3

Nella funzione con l'errore, Result è un TObjectList<T>, dove T è una certa sottoclasse di TItem, ma il compilatore non sa cosa classe specifica è. Il compilatore deve compilarlo in modo che sia sicuro di eseguire per qualsiasi valore di T. Questo potrebbe non essere compatibile con il tipo di argomento LoadAll, che richiede uno TObjectList<TItem>, quindi il compilatore rifiuta il codice.

Supponiamo che T sia TItemDescendant e che il compilatore consenta la compilazione e l'esecuzione del codice difettoso. Se LoadAll chiama AList.Add(TItem.Create), quindi AList finirà per contenere qualcosa che non è un TItemDescendant, anche se è un TObjectList<TItemDescendant>. Tiene un oggetto di un tipo diverso da quello che dice il parametro di tipo generico.

Solo perché S è un sottotipo di T non significa che X<S> è un sottotipo di X<T>.

+0

Hai ragione, non ho guardato la cosa da questo lato. Hai qualche suggerimento su come refactoring questo codice? Per me è importante avere un'interfaccia come LoadAll e restituire l'elenco dei risultati o passare l'elenco dei risultati come parametro per TItems.LoadAll, ma il lavoro vero è fatto da 'TItemsLoader'. TItem è solo un livello indiretto. I TIEM "sanno" che tipo di articoli avranno. TItemsLoader dovrebbe sapere solo che gli elementi sono TItem o il suo discendente. – robertw

+1

Credo che l'altra risposta con l'interfaccia 'IItemsLoader', è ciò che è necessario modificare. Tuttavia, Rob l'ha spiegato esattamente e ha risposto alla tua domanda diretta. –

4

è necessario utilizzare una versione generica del caricatore così:

type 
    TItem = class 
end; 

type 
    IItemsLoader<T: TItem> = interface 
    procedure LoadAll(AList : TObjectList<T>); 
end; 

type 
    TItemsLoader<T: TItem> = class(TInterfacedObject, IItemsLoader<T>) 
public 
    procedure LoadAll(AList : TObjectList<T>); 
end; 

type 
    IItems<T : TItem> = interface 
    function LoadAll : TObjectList<T>; 
end; 

type 
    TItems<T : TItem> = class(TInterfacedObject, IItems<T>) 
    private 
    FItemsLoader : TItemsLoader<T>; 
    public 
    constructor Create; 
    destructor Destroy; override; 
    function LoadAll : TObjectList<T>; 
end; 


implementation 

{$R *.dfm} 

procedure TItemsLoader<T>.LoadAll(AList: TObjectList<T>); 
begin 
    /// some stuff with AList 
end; 

{ TItems<T> } 

constructor TItems<T>.Create; 
begin 
    FItemsLoader := TItemsLoader<T>.Create; 
end; 

destructor TItems<T>.Destroy; 
begin 
    FItemsLoader.Free; 
    inherited; 
end; 

function TItems<T>.LoadAll: TObjectList<T>; 
begin 
    Result := TObjectList<T>.Create(); 

    /// Error here 
    FItemsLoader.LoadAll(Result); 
end; 
+0

Nel mio concetto 'loader 'dovrebbe funzionare solo su TItem. Ma proverò a cambiarlo. – robertw

+0

Funzionerà con TItem e anche con qualsiasi altra sottoclasse, poiché la definizione generica ha il vincolo. –