2010-02-16 16 views
7

Ho una semplice domanda sui delegati .net. Dire che ho qualcosa di simile:delegati C#, tempo di risoluzione riferimento

public void Invoke(Action<T> action) 
    { 
     Invoke(() => action(this.Value)); 
    } 

    public void Invoke(Action action) 
    { 
     m_TaskQueue.Enqueue(action); 
    } 

La prima funzione racchiude un riferimento a this.Value. Durante il runtime, quando viene chiamato il primo metodo con parametro generico, fornirà in qualche modo this.Value al secondo, ma come? Questi sono venuto in mente:

  • chiamata per valore (struct) - il valore corrente di this.Value viene passato, per cui se il m_TaskQueue esegue esso 5 minuti più tardi, il valore non sarà nel suo stato di recente, si sarà qualunque cosa fosse al momento del primo riferimento.
  • Chiama con riferimento (tipo di riferimento) - quindi il più recente stato di Value farà riferimento durante l'esecuzione di azione, ma se cambio this.Value ad un altro riferimento prima dell'esecuzione di azione, sarà ancora rivolta verso la vecchia di riferimento
  • Chiamata per nome (entrambi) - dove this.Value verrà valutato quando viene richiamata l'azione. Credo che l'implementazione effettiva sarebbe in possesso di un riferimento a this quindi valutare Value su quello durante l'esecuzione effettiva del delegato poiché non vi è alcuna chiamata per nome.

Suppongo che sarebbe Call of name style ma non ho trovato alcuna documentazione, chiedendomi se si tratta di un comportamento ben definito. Questa classe è qualcosa come un attore in Scala o Erlang, quindi ho bisogno che sia sicuro. Non desidero immediatamente la funzione per dereferenziare Value, che verrà eseguita in un thread sicuro per this oggetto da m_TaskQueue.

risposta

18

Lasciatemi rispondere alla tua domanda descrivendo il codice che effettivamente generiamo per questo. Rinominerò il tuo altro metodo Invoke con il confuso nome; non è necessario capire cosa sta succedendo qui.

Supponiamo che hai detto

class C<T> 
{ 
    public T Value; 
    public void Invoke(Action<T> action) 
    { 
     Frob(() => action(this.Value)); 
    } 
    public void Frob(Action action) 
    { // whatever 
    } 
} 

Il compilatore genera il codice come se si fosse effettivamente scritto:

class C<T> 
{ 
    public T Value; 

    private class CLOSURE 
    { 
    public Action<T> ACTION; 
    public C<T> THIS; 
    public void METHOD() 
    { 
     this.ACTION(this.THIS.Value); 
    } 
    } 

    public void Invoke(Action<T> action) 
    { 
     CLOSURE closure = new CLOSURE(); 
     closure.THIS = this; 
     closure.ACTION = action; 
     Frob(new Action(closure.METHOD)); 
    } 
    public void Frob(Action action) 
    { // whatever 
    } 
} 

Ritiene che la risposta alla tua domanda?

+0

Grazie mille, è cristallino ora :) –

7

Il delegato memorizza un riferimento alla variabile, non il valore di essa. Se si desidera mantenere il valore corrente allora (ammesso che è un tipo di valore) è necessario effettuare una copia locale di esso:

public void Invoke(Action<T> action) 
{ 
    var localValue = this.Value; 
    Invoke(() => action(localValue)); 
} 

Se si tratta di un tipo di riferimento mutabile si potrebbe fare un clone locale/copia profonda .

+1

Spina Eric Lippert obbligatoria: http://blogs.msdn.com/ericlippert/archive/2009/11/12/closeoverover-the-loop-variable-considered-harmful.aspx –

3

La vera chiave è ricordare che lo scope è lessicale; è qualcosa di cui il compilatore si prende cura. Quindi acquisisce le variabili , non i loro valori . Se quei valori sono tipi di valore o tipi di riferimento è completamente un'altra questione.

Forse un esempio un po 'più estremo di alterare il comportamento del delegato aiuterà:

var myVariable = "something"; 
Action a =() => Console.WriteLine(myVariable); 
myVariable = "something else entirely" 
a(); 

stampe "qualcosa di completamente diverso". In questa luce, non importa quante volte avvolgi, salvi o sposti la funzione; si riferisce ancora alla variabile che racchiude. Quindi, in breve, ciò che conta è il valore della variabile inclusa quando il delegato viene effettivamente eseguito.

Problemi correlati