Se ho le seguenti interfacce e una classe che li implementa - '! Bad'In Delphi, come è possibile verificare se un riferimento IInterface implementa un'interfaccia derivata ma non esplicitamente supportata?
IBase = Interface ['{82F1F81A-A408-448B-A194-DCED9A7E4FF7}']
End;
IDerived = Interface(IBase) ['{A0313EBE-C50D-4857-B324-8C0670C8252A}']
End;
TImplementation = Class(TInterfacedObject, IDerived)
End;
Il codice seguente stampa -
Procedure Test;
Var
A : IDerived;
Begin
A := TImplementation.Create As IDerived;
If Supports (A, IBase) Then
WriteLn ('Good!')
Else
WriteLn ('Bad!');
End;
Questo è un po 'fastidioso ma comprensibile. I supporti non possono eseguire il cast in IBase perché IBase non si trova nell'elenco dei GUID supportato da TImplementation. Può essere risolto modificando la dichiarazione -
TImplementation = Class(TInterfacedObject, IDerived, IBase)
Eppure, anche senza fare che io so già che A implementa IBase perché A è un IDerived ed un IDerived è un IBase. Quindi, se lascio la verifica che posso lanciare un e tutto andrà bene -
Procedure Test;
Var
A : IDerived;
B : IBase;
Begin
A := TImplementation.Create As IDerived;
B := IBase(A);
//Can now successfully call any of B's methods
End;
Ma ci imbattiamo in un problema quando abbiamo iniziare a mettere IBases in un contenitore generico - TInterfaceList per esempio. Può contenere solo IInterfaces quindi dobbiamo fare un po 'di casting.
Procedure Test2;
Var
A : IDerived;
B : IBase;
List : TInterfaceList;
Begin
A := TImplementation.Create As IDerived;
B := IBase(A);
List := TInterfaceList.Create;
List.Add(IInterface(B));
Assert (Supports (List[0], IBase)); //This assertion fails
IBase(List[0]).DoWhatever; //Assuming I declared DoWhatever in IBase, this works fine, but it is not type-safe
List.Free;
End;
Mi piacerebbe molto avere una sorta di affermazione per la cattura di tutti i tipi non corrispondenti - questo genere di cose si possono fare con oggetti utilizzando l'operatore è, ma che non funziona per le interfacce. Per varie ragioni, non voglio aggiungere esplicitamente IBase all'elenco delle interfacce supportate. C'è un modo in cui posso scrivere TImplementation e l'asserzione in modo tale che valuterà il vero iff hard-casting IBase (List [0]) è una cosa sicura da fare?
Edit:
Come è venuto in una delle risposte, sto aggiungendo i due principali motivi che non voglio aggiungere IBase alla lista delle interfacce che implementa TImplementation.
In primo luogo, in realtà non risolve il problema. Se, in Test2, l'espressione:
Supports (List[0], IBase)
restituisce vero, questo non significa che sia sicuro per eseguire un hard-cast. QueryInterface può restituire un puntatore diverso per soddisfare l'interfaccia richiesta. Per esempio, se TImplementation implementa in modo esplicito sia IBase e IDerived (e IInterface), quindi l'affermazione passeranno con successo:
Assert (Supports (List[0], IBase)); //Passes, List[0] does implement IBase
Ma immaginate che qualcuno aggiunge erroneamente un elemento alla lista come IInterface
List.Add(Item As IInterface);
L'asserzione passa ancora - l'elemento implementa ancora IBase, ma il riferimento aggiunto alla lista è solo un IInterface - il suo hard-casting su un IBase non produrrebbe alcunché di ragionevole, quindi l'asserzione non è sufficiente per verificare se il seguente hard -cast è sicuro. L'unico modo che è garantito per il lavoro sarebbe quella di utilizzare un come-fuso o supporti:
(List[0] As IBase).DoWhatever;
Ma questo è un costo delle prestazioni frustrante, in quanto è destinato ad essere la responsabilità del codice di aggiunta di elementi all'elenco assicurati che siano del tipo IBase - dovremmo essere in grado di assumerlo (da qui l'affermazione da prendere se questa ipotesi è falsa).L'affermazione non è nemmeno necessaria, tranne per cogliere errori successivi se qualcuno cambia alcuni dei tipi. Il codice originale da cui proviene questo problema è anche abbastanza critico dal punto di vista delle prestazioni, quindi un costo per le prestazioni che raggiunge poco (si cattura ancora solo tipi non corrispondenti durante l'esecuzione, ma senza la possibilità di compilare una build di rilascio più veloce) è qualcosa che preferisco evitare .
Il secondo motivo è che voglio essere in grado di confrontare i riferimenti per l'uguaglianza, ma questo non può essere fatto se lo stesso oggetto di implementazione è detenuto da riferimenti diversi con offset VMT diversi.
Modifica 2: Ampliato la modifica sopra riportata con un esempio.
Modifica 3: Nota: la domanda è: come posso formulare l'asserzione in modo che l'hard-cast sia sicuro se passa l'asserzione, non come evitare l'hard-cast. Ci sono modi per fare il passo del cast in modo diverso, o per evitarlo completamente, ma se c'è un costo delle prestazioni di runtime, non posso usarli. Voglio tutto il costo del controllo entro l'asserzione in modo che possa essere compilato in seguito.
Detto questo, se qualcuno può evitare del tutto il problema senza costi di prestazioni e nessun pericolo di controllo del tipo sarebbe fantastico!
Nei miei test l'offset VMT in GetInterfaceTable() era uguale anche se sono stati implementati due GUID diversi: ha senso se un'interfaccia "eredita" dall'altra, quindi la base punta solo alla prima parte della stessa tabella . – mghie
Era in codice win32? Ho scoperto che ogni interfaccia che ho aggiunto esplicitamente a TImplementation ha aumentato la dimensione dell'istanza della classe di 4 byte - un puntatore VMT aggiuntivo. Anche ogni GUID passato ai supporti, anche da interfacce ereditate, restituirebbe un indirizzo univoco. – David
Il motivo per cui lo chiedo è che .net Delphi potrebbe comportarsi in modo diverso - Aggiungerò un tag win32 – David