2010-01-20 5 views
12

questo codice emette "valore in uscita".Come ottenere un valore attraverso un parametro out/ref da un metodo che genera un'eccezione?

class P 
{ 
    public static void Main() 
    { 
    string arg = null; 
    try 
    { 
     Method(out arg); 
    } 
    catch 
    { 
    } 
    Console.WriteLine(arg); 
    } 
    public static void Method(out string arg) 
    { 
    arg = "out value"; 
    throw new Exception(); 
    } 
} 

ma questo no.

class P 
{ 
    public static void Main() 
    { 
    object[] args = new object[1]; 
    MethodInfo mi = typeof(P).GetMethod("Method"); 
    try 
    { 
     mi.Invoke(null, args); 
    } 
    catch 
    { 
    } 
    Console.WriteLine(args[0]); 
    } 
    public static void Method(out string arg) 
    { 
    arg = "out value"; 
    throw new Exception(); 
    } 
} 

come posso ottenere sia "value out" e un un'eccezione quando utilizzando la riflessione?

+3

Bella domanda. Ma non si dovrebbe fare affidamento sul valore 'out' se un metodo viene lanciato. –

+1

+1, ottima domanda, ovviamente ho dovuto provarlo :) Vorrei ipotizzare che la tua variabile originale non venga passata alla funzione invocata, ottiene una copia e quella copia viene riflessa nell'originale in caso di successo completamento (che ovviamente non accade). – slugster

+0

@slugster: la tua speculazione è corretta. Sospetto che non ci sia modo di farlo con la riflessione. –

risposta

-1

Il parametro out non è definito se il metodo genera un'eccezione. Puoi vedere questo non inizializzandolo su null nel primo esempio, quindi il codice non verrà compilato.

Quindi, ha senso che il metodo Invoke non restituisca il valore non definito se il metodo genera un'eccezione.

+0

"Il parametro out non è definito se il metodo genera un'eccezione." Puoi citare le specifiche per questo? "Puoi vedere questo non inizializzandolo su null nel primo esempio, quindi il codice non verrà compilato." Questa non è una prova per "non definita" del risultato. Dimostra solo che non è "assegnato definitivamente" in base alle regole del compilatore C#. "Non assegnato definitivamente" non significa indefinito o non assegnato. È una cosa fondamentalmente diversa. –

+2

>> "Il parametro out non è definito se il metodo genera un'eccezione." Perché? Sì, se si rimuove l'inizializzazione, il codice non verrà compilato. Ma non a causa della presenza di throw in Method(), ma a causa del blocco catch vuoto in Main, cioè non tutti i percorsi di esecuzione inizializzano un valore di arg prima dell'utilizzo effettivo. –

+0

-1, la variabile out non è indefinita: esegui il codice e vedrai. – slugster

1

L'eccezione ha ignorato il codice in MethodInfo.Invoke() che copia il valore di [out] dallo stack frame nell'array di oggetti. Il valore sul frame dello stack creato da Invoke() si comporta esattamente come nel primo frammento. Ma è qui che finiscono le somiglianze.

1

L'unico modo è sovraccaricare il metodo in un modo che renda conto della possibilità di un'eccezione e quindi passarne uno in "just in case". Quanto segue produce ciò che penso tu stia cercando. Il problema, a quanto ho capito, è che la riflessione non esegue la manipolazione diretta degli indirizzi passati per riferimento. Gli indirizzi non sono interessati fino a quando il punto finale del metodo non viene raggiunto senza eccezioni. Probabilmente una protezione della memoria o uno schema di sicurezza della memoria dalla MS.

class P 
    { 
     public static void Main() 
     { 
      object[] args = { "1", new Exception()}; 
      MethodInfo mi = typeof(P).GetMethod("Method"); 
      try 
      { 
       mi.Invoke(null, args); 
      } 
      catch 
      { 
      } 
      Console.WriteLine(args[0].ToString()); 
      Console.WriteLine(args[1].ToString()); 
     } 
     public static void Method(ref string arg, ref Exception ex) 
     { 
      try 
      { 
       arg = "out value"; 
       throw new Exception(); 
      } 
      catch (Exception exc) 
      { 
       ex = exc; 
      } 
     } 
} 
+0

Non penso che questo risolva il problema fondamentale. Se avessimo quel tipo di controllo per il metodo invocato, non useremmo la riflessione. Il punto è che quel tipo di metodo potrebbe esistere altrove e potremmo aver bisogno di chiamarlo usando la riflessione. Come lo faremmo? Non penso sia possibile. –

+0

Sono d'accordo che non è possibile nel modo descritto. Ho capito da post che aveva il controllo del metodo riflesso, quindi ho pensato di proporre un work-around. –

0

Vorrei proporre di cambiare il metodo per restituire l'oggetto risultato anziché il parametro out. L'oggetto risultato può contenere un'eccezione e anche il valore del tuo arg.

+0

Come ho notato nel commento nella risposta di @ Joel, la domanda è fondamentale. Cosa succede se non avessimo alcun controllo sul metodo di destinazione. C'è un metodo arbitrario e dobbiamo chiamarlo usando la riflessione. Come lo faremmo? –

0

Se il problema è, come si rileva che si è verificata un'eccezione e si sta lavorando con un'applicazione Windows Form, si è tentato di esaminare l'evento di eccezione Thread e combinarlo con SetUnhandledExceptionMode()?

Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException); 
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); 

static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)   
{    
    HandleException(e.Exception);   
} 
+0

Dov'è il valore del parametro di uscita? Penso che tu abbia frainteso la domanda. Non si tratta di prendere l'eccezione. Il problema è che, quando si usa il riflesso, se il callee lancia, il valore del parametro di uscita non viene assegnato. Questo non risolve il problema. –