2015-09-15 16 views
10

Una domanda (diversa?) Su come viene applicato l'ambito variabile in relazione alle chiusure. Ecco un esempio minimo:Apertura variabile chiusura C#

public class Foo 
{ 
    public string name; 
    public Foo(string name) 
    { 
     this.name = name; 
    } 
} 


public class Program 
{ 
    static Action getAction(Foo obj) 
    { 
     return() => Console.WriteLine(obj.name); 
    } 

    static void Main(string[] args) 
    { 
     Foo obj1 = new Foo("x1");  
     Action a = getAction(obj1); 
     obj1 = new Foo("x2");   
     a();       
    } 
} 

Questa stampa x1. Può essere spiegato come:

getAction restituisce una funzione anonima che ha una chiusura che racchiude la variabile obj. obj ha lo stesso riferimento valore come obj1 ma la sua relazione con obj1 termina lì poiché la chiusura include solo obj. In altre parole, qualsiasi valore assunto da obj1 in seguito non influisce sulla chiusura. Pertanto, ogni volta che viene richiamato a (ad esempio, a viene inviato a un'altra funzione), verrà sempre stampato x1.

Ora le mie domande sono:

  1. è la spiegazione di cui sopra è corretto?
  2. Non ho in mente uno scenario specifico ma cosa succederebbe se volessimo che il programma stampasse x2 (ad esempio chiusura per racchiudere un ambito esterno)? Potrebbe essere fatto (o non ha senso nemmeno tentare)?
+0

La tua spiegazione sembra ovvia perché hai due diverse variabili che inizialmente si riferiscono allo stesso oggetto ma poi assegni un oggetto diverso a uno di loro, che non ha alcun effetto sull'altro. Immagino che dichiarare il tuo parametro 'obj' come' ref' possa indirizzare la tua seconda domanda, ma non ne sono sicuro al 100%. – jmcilhinney

+0

@jmcilhinney Non è consentito acquisire il parametro 'ref' in chiusura. – PetSerAl

+0

@PetSerAl, immagino che potrebbe essere quello di evitare esplicitamente la situazione descritta qui, o almeno di evitare l'apparenza che potrebbe essere possibile. – jmcilhinney

risposta

11

Consideriamo:

static Action getAction(Foo obj) 
{ 
    return() => Console.WriteLine(obj.name); 
} 

La chiusura è sul parametro obj; questo obj è un riferimento passato per valore, quindi se un chiamante fa:

x = someA(); 
var action = getAction(x); 
x = someB(); // not seen by action 

allora la chiusura è ancora al di sopra del valore originale, perché il riferimento (non l'oggetto) è copiati quando passa a getAction.

Si noti che se il chiamante modifica i valori sull'oggetto originale, questo si vedrà con il metodo:

x = someA(); 
var action = getAction(x); 
x.name = "something else"; // seen by action 

All'interno del metodo getAction, è fondamentalmente:

var tmp = new SomeCompilerGeneratedType(); 
tmp.obj = obj; 
return new Action(tmp.SomeCompilerGeneratedMethod); 

con :

class SomeCompilerGeneratedType { 
    public Foo obj; 
    public void SomeCompilerGeneratedMethod() { 
     Console.WriteLine(obj.name); 
    } 
} 
+0

Immagino tu intenda "tmp.SomeCompilerGeneratedMethod'. Non 'obj.SomeCompilerGeneratedMethod' –

+0

@SriramSakthivel davvero, grazie; editò –

+0

Grazie .. questo ha confermato la mia comprensione di come l'acquisizione funziona qui. La mia seconda domanda è forse la più importante - come si può "espandere" l'ambito di chiusura, ad es. se si volesse avere 'obj1' incluso nello scope di chiusura come potrebbe essere fatto? Presumo che questo potrebbe essere fatto se potessimo passare la variabile di riferimento come un 'ref', ma questo non sembra essere permesso. – ubi

3

Risposta breve: La spiegazione è corretta e se si vuole modificare il valore x1-x2 poi si deve cambiare l'oggetto specifico che viene passato all'azione.

obj1.name = 'x2'

Quando un oggetto si passa a una funzione come parametro copia il riferimento (puntatore) alla obj.

A quel tempo si dispone di un oggetto e due riferimenti;

  • Il Foo obj1 che è la variabile nella Main e
  • Il Foo obj che è la variabile nella getAction

Ogni volta che si sceglie di impostare un altro oggetto (o nullo) per obj1 non lo farà influire sul secondo riferimento nello getAction.

3

Ecco l'IL generato per principale:

IL_0000: ldstr  "x1" 
IL_0005: newobj  UserQuery+Foo..ctor 
IL_000A: stloc.0  // obj1 
IL_000B: ldloc.0  // obj1 
IL_000C: call  UserQuery.getAction 
IL_0011: stloc.1  // a 
IL_0012: ldstr  "x2" 
IL_0017: newobj  UserQuery+Foo..ctor 
IL_001C: stloc.0  // obj1 
IL_001D: ldloc.1  // a 
IL_001E: callvirt System.Action.Invoke 
IL_0023: ret  

da cui deduco che quando si chiama getAction(), si crea il metodo con i valori in esso per obj1, quando si sta creando una nuova istanza e invocando il delegato, a causa della chiusura Ha precedente valore nel suo metodo creato compilatore, a causa della quale la stampa x1

Quando si sta chiamando getAction(obj1), Foo obj fa adesso riferimento ad new Foo ("x1") ``, allora si sta facendo obj1 = new Foo("x2"), ora obj1 è ha riferimento di new Foo("x2") ma Foo obj del getAction(Foo obj) è ancora di riferimento su new Foo("x1")

obj1 = new Foo("x1") // obj1 is referencing to ----> Foo("x1") memory location 
getAction(obj1) // --> getAction(Foo obj) obj is referencing to Foo("x1") 
obj1 = new Foo("x2") // obj1 is now referencing to----> Foo("x2") memory location 
// but in getAction(Foo obj) obj is still referencing to Foo("x1") 
1

È possibile riscrivere il codice per

public class Program 
{ 
    static void Main(string[] args) 
    { 
     Foo obj1 = new Foo("x1"); 
     // rewrite of 
     // Action a = GetAction(obj1); 
     Foo obj = obj1; 
     Action a =() => Console.WriteLine(obj.name); 

     obj1 = new Foo("x2");   
     a();       
    } 
} 

Questo è ciò che accade internamente. Assegnare il riferimento a obj e creare un'azione che fa riferimento a obj.

1

È spiegazione è corretta ed è fondamentalmente un modo di riformulare ciò che è scritto nelle specifiche del linguaggio C# a Section 5.1.4 (riportata per completezza, sottolineatura mia):

Un parametro dichiarato senza un ref o modificatore di fuori è un parametro .

Un parametro valore viene in esistenza su invocazione dell'elemento funzione (metodo, costruttore di istanza, di accesso, o operatore) (punto 7.4) per cui il parametro appartiene, e è inizializzato con il valore dell'argomento dato nell'invocazione. Un parametro di valore cessa di esistere al ritorno del membro della funzione.

Ai fini della verifica definitiva dell'assegnazione, un parametro di valore è considerato inizialmente assegnato.

Problemi correlati