2016-07-13 48 views
7

Esaminando un albero di espressioni, è possibile ottenere il valore di una costante, campo di istanza e proprietà ma non una variabile locale definita in un metodo.Come accedere al valore di una variabile locale da un albero di espressioni

L'esecuzione di quanto segue genererà 1, 2, 3 (dalla costante, campo di istanza e proprietà) quindi un'eccezione poiché non so come ottenere l'istanza su cui viene dichiarato FieldInfo per chiamare GetValue () per la variabile locale.

using System; 
using System.Linq.Expressions; 
using System.Reflection; 

namespace Example 
{ 
    class Program 
    { 
     private int _intField = 2; 

     static void Main() 
     { 
      new Program().Run(); 
      Console.ReadLine(); 
     } 

     private void Run() 
     { 
      IntProp = 3; 
      var intVariable = 4; 
      Test(() => 1); 
      Test(() => _intField); 
      Test(() => IntProp); 
      Test(() => intVariable); 
     } 

     public int IntProp { get; set; } 

     void Test<T>(Expression<Func<T>> func) 
     { 
      var body = func.Body; 

      if (body.NodeType == ExpressionType.Constant) 
      { 
       Console.WriteLine(((ConstantExpression)body).Value); 
      } 
      else 
      { 
       var memberExpression = body as MemberExpression; 

       var @object = memberExpression.Member.DeclaringType == GetType() 
        ? this 
        : null; //Can I do anything here? Instance of the method the variable is defined in? 

       if (memberExpression.Member.MemberType == MemberTypes.Field) 
       { 
        Console.WriteLine(((FieldInfo)memberExpression.Member).GetValue(@object)); 
       } 
       else if (memberExpression.Member.MemberType == MemberTypes.Property) 
       { 
        Console.WriteLine(((PropertyInfo)memberExpression.Member).GetValue(@object)); 
       } 
      } 
     } 
    } 
} 
+4

Semplicemente non si può fare questo. La variabile non esiste nemmeno se il metodo non è in esecuzione. –

+2

Possibile duplicato di [È possibile ottenere variabili locali tramite la riflessione?] (Http://stackoverflow.com/questions/11118084/is-it-possibile-per-impostare-local-variabili- attraverso -risposta) – CarbineCoder

+0

Cosa sono stai cercando di ottenere? Il nome del parametro? Sono un po 'confuso, ma hai sempre la possibilità di impostare una variabile' globale 'prima di invocare il metodo con qualsiasi cosa tu abbia bisogno. –

risposta

3

La variabile locale che è stata catturata dal lambda e inclusa nell'albero delle espressioni, sarà in quel momento un campo su una classe generata dal compilatore. Questo funziona sulla mia versione di .NET:

void Test<T>(Expression<Func<T>> func) 
{ 
    var body = func.Body; 

    if (body.NodeType == ExpressionType.Constant) 
    { 
    Console.WriteLine(((ConstantExpression)body).Value); 
    } 
    else 
    { 
    var memberExpression = (MemberExpression)body; 

    var @object = 
     ((ConstantExpression)(memberExpression.Expression)).Value; //HERE! 

    if (memberExpression.Member.MemberType == MemberTypes.Field) 
    { 
     Console.WriteLine(((FieldInfo)memberExpression.Member).GetValue(@object)); 
    } 
    else if (memberExpression.Member.MemberType == MemberTypes.Property) 
    { 
     Console.WriteLine(((PropertyInfo)memberExpression.Member).GetValue(@object)); 
    } 
    } 
} 

Naturalmente, è anche possibile "barare":

void Test<T>(Expression<Func<T>> func) 
{ 
    Console.WriteLine(func.Compile()()); 
} 
+0

Interessante! Ma se questa è la risposta, ci si deve chiedere quale sia la domanda (cioè penso che l'OP abbia pubblicato un problema X-Y). –

+0

@MatthewWatson Sì, ho preso la domanda come "come posso modificare questo compito su' @ object' per trovare il target/istanza pertinente? ". Tuttavia, non è chiaro se ha davvero bisogno dell'istanza della classe generata dal compilatore (proveniente dalla chiusura (acquisizione della variabile locale)). –

+0

Questo è esattamente ciò di cui avevo bisogno. Questa è esattamente la domanda che avrei dovuto chiedere. – Dan

1

No, non è possibile.

La riflessione non si estende alla lettura dei valori della variabile del metodo.

Gestisce solo i metadati di dichiarazione delle variabili. E anche allora, il compilatore potrebbe aver rimosso la variabile che pensavi di aver dichiarato. Puoi già accedere a proprietà, campi.

+0

Se leggi il suo codice, vedrai che quello che ha è un albero di espressioni contenente una variabile locale _captured_. Una cosa del genere sarà stata trasformata in un campo su alcune classi generate dal compilatore. Certo dovresti essere in grado di leggere il suo valore. Vedi la mia risposta. –

0

No, non è possibile perché quelle variabili semplicemente non sono disponibili al di fuori del metodo il loro ambito.

+0

La variabile locale nel codice del richiedente è stata catturata nella lambda (chiusura), e come tale è sicuramente disponibile. Vedi la mia risposta. Il richiedente non ha fornito molte spiegazioni, quindi è stato necessario leggere il suo codice. –

0

No, non è possibile con la riflessione. Ma ci sono altri metodi per farlo. Ad esempio, ti mostro come esportare una variabile dall'ambito lessicale delle funzioni.

Diciamo che avete un metodo come questo:

private void f(int x) 
{ 
    // your code here 
} 

Hai anche un pezzo di codice come questo:

int x_in_f; 

f(3); 
x_in_f = /* I want to have the value here */; 

per ottenere il valore di f() è necessario esportare una funzione getter. Come f() restituisce void, è possibile restituire il getter. In un caso generale (quando f() ha un tipo di reso non vuoto) è possibile utilizzare un parametro out. Qui sono entrambe le varianti:

private Func<int> f(int x) 
{ 
    // your code here 

    return() => { return x; }; 
} 

o tramite il parametro out:

private void f(int x, out Func<int> g) 
{ 
    // your code here 

    g =() => { return x; }; 
} 

Il codice sarebbe allora:

int x_in_f; 
Func<int> g; 

g = f(3); 

x_in_f = g(); // this will return the value of x as it was passed to f() 

o tramite il parametro out invocano f() come segue:

f(3, out g); 

A quel punto è possibile passare g() intorno ad altre funzioni:

private void h(Func<int> getx) 
{ 
    // your code here 
    int x = getx(); 
    // now you have the value of x inside the h() function 
} 

e l'invocazione con h():

Func<int> g = f(3); 

// ... 

h(g); 

Spero che questo aiuta o almeno mostra come usare le chiusure di aggirare lessicale scoping.

Per i progettisti là fuori, questo è il modello Capacità oggetto. This is a video di Douglas Crockford su come usarlo per la sicurezza in Javascript.Si traduce in C# e altri scopi facilmente, come ho mostrato sopra.