2009-11-29 15 views
14

Stiamo studiando un modello di codifica in C# in cui vorremmo usare una clausola "using" con una classe speciale, il cui metodo Dispose() fa cose diverse a seconda che il corpo "using" sia stato chiuso normalmente o con un'eccezione .Come determinare se viene gestita un'eccezione .NET?

Per quanto mi riguarda, il CLR tiene traccia dell'eccezione corrente gestita fino a quando non viene utilizzata da un gestore "catch". Tuttavia non è del tutto chiaro se queste informazioni sono esposte in alcun modo per il codice di accesso. Sai se lo è, e se sì, come accedervi?

Ad esempio:

using (var x = new MyObject()) 
{ 
    x.DoSomething(); 
    x.DoMoreThings(); 
} 

class MyObject : IDisposable 
{ 
    public void Dispose() 
    { 
     if (ExceptionIsBeingHandled) 
      Rollback(); 
     else 
      Commit(); 
    } 
} 

Questo sembra quasi System.Transactions.TransactionScope, tranne che il successo/insuccesso non è determinato da una chiamata a x.Complete(), ma piuttosto sulla base se il corpo using è stata chiusa normalmente.

+2

Penso che devi chiederti perché stai cercando di farlo. Il pattern Dispose() non è inteso per implementare la logica di controllo elevando le eccezioni. –

+1

È sempre un buon punto mettere in discussione l'intera idea. Apprezzo che questo non è il modo in cui "usare" è destinato a essere usato. Apprezzo che possa portare a un codice errato. Sono ancora interessato a una risposta :) –

+1

Perché questa domanda è stata downvoted? – Olli

risposta

11

http://www.codewrecks.com/blog/index.php/2008/07/25/detecting-if-finally-block-is-executing-for-an-manhandled-exception/ descrive un "trucco" per rilevare se il codice viene eseguito in modalità di gestione delle eccezioni o meno. Utilizza Marshal.GetExceptionPointers per verificare se un'eccezione è "attiva".

Ma tenere a mente:

Annotazioni

GetExceptionPointers è esposto per il supporto del compilatore di gestire unica eccezione strutturata (SEH). NotaNota:

Questo metodo utilizza SecurityAction.LinkDemand per impedire che venga chiamato da codice non attendibile; solo il chiamante immediato deve disporre dell'autorizzazione SecurityPermissionAttribute.UnmanagedCode. Se il codice può essere chiamato da codice parzialmente attendibile, non passare l'input dell'utente ai metodi della classe Marshal senza convalida. Per limitazioni importanti sull'utilizzo del membro LinkDemand, vedere Demand vs. LinkDemand.

+5

Questa è probabilmente l'unica risposta reale alla tua domanda, ma sembra una cattiva idea da perseguire. –

+0

Grazie, posto. @Programming Hero: possibilmente. Non posso negare che sembra così. Faremo un tentativo in un piccolo progetto di test e vediamo se ci sono grossi problemi con questo approccio. –

+2

Se segui questo percorso, dai uno sguardo al seguente blog: http://geekswithblogs.net/akraus1/archive/2008/04/08/121121.aspx – Joe

2

Un'istruzione using è solo zucchero sintattico per un blocco try finally. È possibile ottenere ciò che si vuole scrivendo il tentativo finalmente fuori in pieno e poi l'aggiunta di un'istruzione catch per gestire il vostro caso particolare:

try 
{ 
    IDisposable x = new MyThing(); 
} 
catch (Exception exception) // Use a more specific exception if possible. 
{ 
    x.ErrorOccurred = true; // You could even pass a reference to the exception if you wish. 
    throw; 
} 
finally 
{ 
    x.Dispose(); 
} 

All'interno MyThing si può fare questo se si vuole, ad esempio:

class MyThing : IDisposable 
{ 
    public bool ErrorOccurred() { get; set; } 

    public void Dispose() 
    { 
     if (ErrorOccurred) { 
      RollBack(); 
     } else { 
      Commit(); 
     } 
    } 
} 

Nota: devo anche chiedermi perché vuoi farlo. Ha un certo odore di codice. Il metodo Dispose è destinato a ripulire le risorse non gestite, non a gestire le eccezioni. Probabilmente starai meglio scrivendo il codice di gestione delle eccezioni nel blocco catch, non nel dispose e, se hai bisogno di condividere il codice, fai alcune utili funzioni di aiuto che puoi chiamare da entrambi i posti.

Ecco un modo migliore di fare ciò che si vuole:

using (IDisposable x = new MyThing()) 
{ 
    x.Foo(); 
    x.Bar(); 
    x.CommitChanges(); 
} 

class MyThing : IDisposable 
{ 
    public bool IsCommitted { get; private set; } 

    public void CommitChanges() 
    { 
     // Do stuff needed to commit. 
     IsCommitted = true; 
    } 

    public void Dispose() 
    { 
     if (!IsCommitted) 
      RollBack(); 
    } 
} 
+0

Ah, avrei dovuto dirlo. Il punto è che il codice che va in "catch" e "finally" è identico ma anche non banale. Il punto è spostare questo codice altrove. –

+0

Questo non è un problema. Il codice di richiesta viene chiamato in entrambi i casi, ma nel codice di cattura è possibile eseguire il codice * extra * prima di chiamare. Ad esempio, puoi impostare un valore booleano all'interno di x, che può essere controllato quando viene chiamato Dispose. –

+0

Grazie per i vostri sforzi, ma se dobbiamo ancora inserire una clausola try/catch "normale", non c'è alcun punto in questo - come osservate giustamente, le cose che vengono in "if (ErrorOccurred)/else' dovrebbero basta andare all'interno di 'catch' /' finally'. L'idea è di salvare ripetitivo try/catch/finally con un 'using', perché ridurrebbe il codice di default in tutto il sito. –

4

Queste informazioni non sono disponibili.

Vorrei utilizzare un modello simile a quello utilizzato dalla classe DbTransaction: ovvero, la classe IDisposable deve implementare un metodo analogo a DbTransaction.Commit(). Il metodo Dispose può quindi eseguire una logica diversa a seconda che sia stato richiamato o meno il commit (nel caso di DbTransaction, la transazione verrà annullata se non è stata stabilita esplicitamente).

Gli utenti della vostra classe sarebbero quindi utilizzare il seguente modello, simile ad un tipico DbTransaction:

using(MyDisposableClass instance = ...) 
{ 
    ... do whatever ... 

    instance.Commit(); 
} // Dispose logic depends on whether or not Commit was called. 

EDIT Vedo che hai modificato la tua domanda per mostrare che sei a conoscenza di questo modello (la tua esempio utilizza TransactionScope). Tuttavia penso che sia l'unica soluzione realistica.

2

Questa non sembra essere una cattiva idea; semplicemente non sembra essere l'ideale in C# /. NET.

In C++ è presente una funzione che consente al codice di rilevare se è stato chiamato a causa di un'eccezione. Questo è di grande importanza nei distruttori RAII; è una cosa banale per il distruttore scegliere di commettere o interrompere a seconda che il flusso di controllo sia normale o eccezionale. Penso che sia un approccio abbastanza naturale da prendere, ma la mancanza di supporto integrato (e la natura moralmente dubbia della soluzione alternativa, mi sembra piuttosto dipendente dall'implementazione) probabilmente significa che dovrebbe essere adottato un approccio più convenzionale.

7

Non una risposta alla domanda, ma solo una nota che non ho mai finito con l'hack "accettato" nel codice reale, quindi è ancora ampiamente testato "in the wild". Invece siamo andati per qualcosa di simile:

DoThings(x => 
{ 
    x.DoSomething(); 
    x.DoMoreThings(); 
}); 

dove

public void DoThings(Action<MyObject> action) 
{ 
    bool success = false; 
    try 
    { 
     action(new MyObject()); 
     Commit(); 
     success = true; 
    } 
    finally 
    { 
     if (!success) 
      Rollback(); 
    } 
} 

Il punto chiave è che è compatto come il "utilizzando" ad esempio nella questione, e non utilizza alcun hack.

Tra gli inconvenienti vi è una penalità di prestazioni (completamente trascurabile nel nostro caso) e F10 che entra nello DoThings quando in realtà lo desidero semplicemente passare direttamente a x.DoSomething(). Entrambi molto minori.

+3

Questo è molto meno attraente in VS2010 a causa di bug IntelliSense a che fare con i metodi anonimi ... https://connect.microsoft.com/VisualStudio/feedback/details/557224/intellisense-completely-fails-for-collection-inside- anonymous-metodo? wa = wsignin1.0 –

Problemi correlati