2009-04-29 18 views
9

Una cosa bella dei metodi anonimi è che posso usare variabili locali nel contesto di chiamata. C'è qualche ragione per cui questo non funziona per i parametri out e i risultati delle funzioni?Ambito dei metodi anonimi

function ReturnTwoStrings (out Str1 : String) : String; 
begin 
    ExecuteProcedure (procedure 
        begin 
         Str1 := 'First String'; 
         Result := 'Second String'; 
        end); 
end; 

Esempio molto artificiale, naturalmente, ma mi sono imbattuto in alcune situazioni in cui ciò sarebbe stato utile.

Quando provo a compilarlo, il compilatore si lamenta che "non può acquisire i simboli". Inoltre, ho avuto un errore interno una volta quando ho provato a farlo.

EDIT Ho appena realizzato che funziona per i parametri normali come

... (List : TList) 

non è che come problematico, in quanto gli altri casi? Chi garantisce che il riferimento punta ancora a un oggetto vivo ogni volta che viene eseguito il metodo anonimo?

+0

Utilizzare il puntatore anziché i parametri di riferimento. – MajidTaheri

risposta

20

Parametri di var e out e la variabile Result non può essere catturata perché la sicurezza di questa operazione non può essere verificata staticamente. Quando la variabile Result è di un tipo gestito, come una stringa o un'interfaccia, la memoria viene effettivamente allocata dal chiamante e un riferimento a questa memoria viene passato come parametro implicito; in altre parole, la variabile Result, a seconda del suo tipo, è proprio come un parametro out.

La sicurezza non può essere verificata per il motivo indicato da Jon. La chiusura creata da un metodo anonimo può sopravvivere all'attivazione del metodo in cui è stata creata e può sopravvivere in modo simile all'attivazione del metodo che ha chiamato il metodo in cui è stato creato.Pertanto, qualsiasi parametro var o out o le variabili risultanti catturate potrebbero finire orfane e qualsiasi scrittura scritta all'interno della chiusura in futuro corromperà lo stack.

Naturalmente, Delphi non viene eseguito in un ambiente gestito, e non hanno le stesse restrizioni di sicurezza come ad esempio C#. La lingua potrebbe farti fare quello che vuoi. Tuttavia, risulterebbe difficile diagnosticare bug in situazioni in cui è andato storto. Il cattivo comportamento si manifesterebbe come variabili locali in un valore di modifica di routine senza causa prossimale visibile; sarebbe anche peggio se il riferimento al metodo fosse chiamato da un altro thread.

Questo sarebbe abbastanza difficile da eseguire il debug. Anche i breakpoint della memoria hardware potrebbero essere uno strumento relativamente scarso, poiché lo stack viene modificato di frequente. Uno avrebbe bisogno di attivare i breakpoint della memoria hardware condizionatamente dopo aver colpito un altro punto di interruzione (ad es. Dopo l'inserimento del metodo). Il debugger Delphi può farlo, ma rischierei di indovinare che la maggior parte delle persone non conosce la tecnica.

Aggiornamento: Per quanto riguarda le aggiunte alla tua domanda, la semantica di passaggio riferimenti esempio per valore è molto diversa tra i metodi che contengono una chiusura (e catturano l'paramete0 e metodi che non contengono una chiusura entrambi i casi. il metodo può mantenere un riferimento all'argomento passato per valore, i metodi che non acquisiscono il parametro possono semplicemente aggiungere il riferimento a un elenco o memorizzarlo in un campo privato

La situazione è diversa con i parametri passati per riferimento perché le aspettative del chiamante sono diversi. Un programmatore che fa questo:

procedure GetSomeString(out s: string); 
// ... 
GetSomeString(s); 

sarebbe estremamente sorpreso se GetSomeString dovesse mantenere un riferimento alla variabile s passata D'altra parte:.

procedure AddObject(obj: TObject); 
// ... 
AddObject(TObject.Create); 

Non è sorprendente che AddObject mantiene un riferimento, in quanto suggerisce il nome stesso che è aggiungendo il parametro a qualche store di stato. Indipendentemente dal fatto che questo store di stato abbia la forma di una chiusura o meno è un dettaglio di implementazione del metodo AddObject.

+0

Perché parli dello stack? Le variabili catturate non vengono archiviate nello stack, ma piuttosto nell'oggetto nascosto, che implementa l'interfaccia. Cioè var M, N: Integer; - se solo N viene usato nel metodo anonimo, allora M diventa stack e N sarà un campo di oggetti nascosti. Non apparirà in pila. Do misundestand qualcosa? – Alex

+0

@Alexander: Barry stava descrivendo la situazione che cosa sarebbe accaduto quando gli era stato permesso di catturare i parametri var e i risultati delle funzioni. Poiché non è consentito, la situazione con sovrascritture dello stack non si verifica. –

+0

+1 @Barry: spiegazione molto migliore di quanto potrei fare. –

3

Il parametro out e il valore restituito sono irrilevanti dopo il ritorno della funzione - come ti aspetteresti che il metodo anonimo si comporti se lo hai catturato ed eseguito successivamente? (In particolare, se si utilizza il metodo anonimo per creare un delegato ma non lo si esegue mai, il parametro out e il valore restituito non verranno impostati al momento della restituzione della funzione.)

I parametri di uscita sono particolarmente difficili: la variabile che gli alias dei parametri out potrebbero anche non esistere prima che tu possa chiamare il delegato in un secondo momento. Ad esempio, si supponga di essere in grado di acquisire il parametro out e restituire il metodo anonimo, ma il parametro out è una variabile locale nella funzione di chiamata ed è in pila. Se il metodo di chiamata viene quindi restituito dopo aver memorizzato il delegato da qualche parte (o restituito), cosa succederebbe quando il delegato è stato finalmente chiamato? Dove scriverà quando è stato impostato il valore del parametro out?

+0

Per quanto riguarda il primo punto: questo è vero per ogni variabile locale che uso non è vero? E al secondo punto: cosa succede se voglio tuttavia che il metodo anonimo produca il risultato della funzione? Potrei facilmente emulare questo usando una variabile locale, usare quella variabile locale nel metodo anonimo e quindi assegnarla a Risultato aftwerwards. – jpfollenius

+0

Giusto per chiarire il mio punto: ciò comporterebbe lo stesso problema se uso il metodo anonmouy come delegato, non è vero? – jpfollenius

+1

No, non è vero per le variabili locali all'interno del metodo stesso. Essi (presumendo che Delphi sia come C#, comunque) vengano catturati posizionandoli invece nell'heap - ogni riferimento alla variabile locale verrà effettivamente inviato tramite un "contenitore" per tutte le variabili locali in quell'ambito. Il compilatore è in grado di farlo per le variabili locali all'interno del metodo perché sa quali variabili verranno catturate - non può farlo per le variabili locali nel codice * che chiama * questo metodo. –

6

Il problema è che la variabile Str1 non è "proprietà" di ReturnTwoStrings, quindi il metodo anonimo non può acquisirla.

Il motivo per cui non è in grado di catturarlo, è che il compilatore non conosce il proprietario finale (da qualche parte nello stack di chiamate verso ReturnTwoStrings) in modo che non possa determinare da dove farlo.

Edit: (Aggiunto dopo un commento di Smasher)

Il nucleo di metodi anonimi è che catturano le variabili (non i loro valori).

Allen Bauer (CodeGear) spiega un po 'di più about variable capturing in his blog.

C'è un C# question about circumventing your problem pure.

+0

+1, motivo molto tecnico però – jpfollenius

0

Sto mettendo questo in una risposta separata perché il tuo EDIT rende la tua domanda davvero diversa.

Io probabilmente estendere questa risposta più tardi come io sono un po 'di fretta per arrivare a un client.

La modifica indica che è necessario rivedere i tipi di valori, i tipi di riferimento e l'effetto di var, out, const e nessun segno di parametri.

Facciamo la cosa tipi di valore prima.

I valori dei tipi di valore vivono nello stack e hanno un comportamento di copia su assegnazione. (proverò a includere un esempio in seguito).

Quando non è presente alcun parametro, il valore effettivo passato a un metodo (procedura o funzione) verrà copiato sul valore locale di tale parametro all'interno del metodo. Quindi il metodo non opera sul valore passato ad esso, ma su una copia.

Quando hai fuori, var o const, quindi nessuna copia avviene: il metodo farà riferimento al valore effettivo passato. Per var, permetterà di cambiare quel valore attuale, per const non lo permetterà. Per uscire, non sarai in grado di leggere il valore reale, ma potrai comunque scrivere il valore reale.

I valori dei tipi di riferimento risiedono nell'heap, quindi per loro non ha importanza se si ha, var, const o nessun segno di parametro: quando si modifica qualcosa, si modifica il valore sull'heap.

Per i tipi di riferimento, è ancora possibile ottenere una copia quando non è presente un contrassegno dei parametri, ma si tratta di una copia di un riferimento che punta ancora al valore sull'heap.

Ecco dove i metodi anonimi si complicano: fanno una cattura variabile. (Barry può probabilmente spiegarlo ancora meglio, ma ci proverò) Nel tuo caso modificato, il metodo anonimo catturerà la copia locale della Lista. Il metodo anonimo funzionerà su quella copia locale, e dal punto di vista del compilatore tutto è dandy.

Tuttavia, il punto cruciale della modifica è la combinazione di "funziona con parametri normali" e "che garantisce che il riferimento continui a puntare a un oggetto attivo ogni volta che viene eseguito il metodo anonimo".

Questo è sempre un problema con i parametri di riferimento, non importa se si utilizzano metodi anonimi o meno.

Per esempio questo:

procedure TMyClass.AddObject(Value: TObject); 
begin 
    FValue := Value; 
end; 

procedure TMyClass.DoSomething(); 
begin 
    ShowMessage(FValue.ToString()); 
end; 

Chi garantisce che quando qualcuno chiama DoSomething, che l'istanza in cui i punti di valore F esiste ancora? La risposta è che devi garantirlo da solo non chiamando DoSomething quando l'istanza di FValue è morta. Lo stesso vale per la tua modifica: non devi chiamare il metodo anonimo quando l'istanza sottostante è morta.

Questa è una delle aree in cui le soluzioni di riferimento contato o garbage collection rendono la vita più facile: c'è l'istanza sarà mantenuto in vita fino a quando l'ultimo riferimento ad esso è andato via (che potrebbero causare esempio per vivere più a lungo di quanto originariamente anticipato!).

Quindi, con la modifica, la domanda cambia in realtà dai metodi anonimi alle implicazioni dell'uso dei parametri tipizzati di riferimento e della gestione a vita in generale.

Speriamo che la mia risposta ti aiuti ad andare in quella zona.

- jeroen

Problemi correlati