2011-01-17 13 views
8

Gibt es eine Möglichkeit, einen Verweis auf eine Ausnahme innerhalb eines finally-Blocks in einem iterator function or property that allow try..finally but not try..catch zu erhalten?Verweis auf eine Ausnahmebedingung in finally-Block in einem Iterator

Ich werde es nicht verwenden, um mit dem Kontrollfluss zu ändern oder zu verwirren, aber würde in der Lage sein, einen Verweis auf die Ausnahme im finally-Block trotzdem zu bekommen (wenn man geworfen wurde), um zu lesen von ihm und möglicherweise fügen Sie Sachen zum Data member hinzu.

Ich verstehe, dass aufgrund der Art des Compilers Klassen aus Iteratoren generiert, ist es wahrscheinlich nicht möglich/erlaubt aus dem gleichen Grund, warum try..catch um eine yield-Anweisung in erster Linie nicht erlaubt ist. Aber ich hoffe immer noch, dass es vielleicht einen Weg (oder sogar einen hässlichen Trick) gibt, um die Ausnahme zu bekommen.

Vereinfachtes Beispiel:

IEnumerable<SomeClass> Something 
get 
{ 
    try 
    { 
    throw new SomeException(); 
    yield return new SomeClass(); 
    } 
    finally 
    { 
    Exception ex = ... // <= TODO - get hold of the exception here [if one was thrown]... 
    } 
} 

Antwort

17

Dies ist eine sehr interessante Frage.

Daran erinnern, dass in Linq viele Standard-Operatoren zur Verfügung gestellt werden, die effektiv miteinander verketten. Es gibt derzeit keine Möglichkeit, die benutzerdefinierte Ausnahmebehandlung um eine innere Sequenz zu wickeln.

So ist mein Vorschlag, eine neue zu schreiben, die Sie eine Aktion angeben können, die jede Ausnahme behandelt, die während der Ausführung von IEnumerator.MoveNext auftritt:

public static class EnumerableExceptions 
{ 
    public static IEnumerable<TItem> Catch<TItem, TEx>(
     this IEnumerable<TItem> source, 
     Action<TEx> handler) where TEx : Exception 
    { 
     using (var enumerator = source.GetEnumerator()) 
     { 
      for (; ;) 
      { 
       try 
       { 
        if (!enumerator.MoveNext()) 
         yield break; 
       } 
       catch (TEx x) 
       { 
        handler(x); 
        yield break; 
       } 

       yield return enumerator.Current; 
      } 
     } 
    } 
} 

So, jetzt angenommen wir dies hatte:

public class NastyException : Exception { } 

public static IEnumerable<String> StringYielder() 
{ 
    yield return "apple"; 
    yield return "banana"; 

    throw new NastyException(); 

    yield return "oracle"; 
    yield return "grapefruit"; 
    yield return "microsoft"; 
} 

Wir würden gerne in der Lage sein, den ganzen Körper in eine try/catch wickeln, die leider illegal ist. Aber was können wir tun, ist die erzeugte Sequenz wickeln:

public static IEnumerable<String> LoggingStringYielder() 
{ 
    return StringYielder().Catch(
     (NastyException ex) => 
      Console.WriteLine("Exception caught: " + ex.StackTrace)); 
} 

Das heißt, erhalte ich eine Sequenz, die durch die „rohen“ StringYielder Methode aufrufen, und dann wende ich den neuen Catch Operator es, die festlegen, was zu tun, wenn Ein bestimmter Ausnahmetyp tritt auf. Hier werde ich nur den Stack-Trace drucken.

Also, wenn ich dies tun:

foreach (var str in LoggingStringYielder()) 
    Console.WriteLine(str); 

Das Programm beendet, ohne abzustürzen, und der Ausgang ist:

apple 
banana 
Exception caught: at ConsoleApplication7.Program.<StringYielder>.. blah 

So, obwohl Sie nicht einen Versuch catch um den Code in das setzen kann Original-Iterator-Methode, können Sie sie jetzt außerhalb der Iterator-Methode umschließen. Es ist wie eine nicht-intrusive Möglichkeit, die Ausnahmebehandlung um den Code zwischen jeweils yield return zu injizieren.

Bonusupdate!

Um wirklich pingelig zu sein, die ich über die Art und Weise, dass letzte Satz lautet:

  • Zum einen können Sie vor dem ersten yield return werfen und es wird auf die gleiche Art und Weise behandelt, wie der Code in dem ersten Aufruf führt zu MoveNext. Also "... der Code vor jeder ..." wäre genauer als "... der Code zwischen jeder ..." gewesen.

  • Zweitens kann ein yield return einen Ausdruck akzeptieren, der ausgewertet werden muss und der während der Auswertung ausgelöst werden kann. Das sollte als Code betrachtet werden, der ausgeführt wird, bevor das yield return auftritt, obwohl es syntaktisch danach erscheint.

1

finally Blöcke werden für immer ausgeführte clean-up gedacht und daher nicht beabsichtigt sind, mit Ausnahmen zu manipulieren - es kann einfach überhaupt keine anhängige Ausnahme sein.

den catch Block mit einem rethrow verwenden sollte für Sie arbeiten:

try 
{ 
    // .. something throws here .. 
} 
catch (Exception ex) 
{ 
    // .. do whatever you need with ex here .. 

    // and pass it on 
    throw; 
} 
+1

Ok, ich habe die Frage aktualisiert, um zu reflektieren, dass ich die Ausnahme erhalten möchte, _wenn man geworfen wurde. Trotzdem, Fang ist nicht erlaubt um den Ertrag ... – KristoferA

5

Wie über den gesamten Code zu bewegen, die eine Ausnahme in einer verschachtelten try/catch erzeugen könnten:

IEnumerable<int> GetFoo() 
{ 
    for (int i = -10; i < 10; i++) 
    { 
     Exception ex = null; 
     try 
     { 
      int result = 0; 
      try 
      { 
       result = 10/i; 
      } 
      catch (Exception e) // Don't normally do this! 
      { 
       ex = e; 
       throw; 
      } 
      yield return result; 
     } 
     finally 
     { 
      if (ex != null) 
      { 
       // Use ex here 
      } 
     } 
    } 
} 

Mit dem Oberhalb des Musters können Sie jedoch alles tun, was Sie brauchen, nur innerhalb des catch-Blocks, was einfacher wäre - Sie könnten den umgebenden try/finally-Block loswerden.

+0

Ich wünschte, ich könnte, aber das würde bedeuten, einige Refactoring. Das Problem ist, dass es verschachtelte Iteratoren gibt, und ich frage einen oder mehrere Iteratoren ab, durchlaufe die Ergebnisse und gebe zurück. (in einer Tonne von Orten). Es kann mit dem obigen Ansatz refaktoriert werden, aber ich möchte mit minimalen Codeänderungen auf Nummer sicher gehen, um Regressionen etc. zu vermeiden. Deshalb bevorzuge ich, wenn ich irgendwie einen Verweis auf die Ausnahme von der CLR im finally-Block bekommen kann. . :) – KristoferA

2

Zu wissen, welche Ausnahme aussteht, wenn überhaupt, während eines Finalizers ist eine nette Fähigkeit. VB.net macht es möglich, obwohl peinlich. C# nicht. In vb.net der Technik ist:

 
    Dim PendingException as Exception = Nothing 
    Try 
    .. Do Whatever 
    PendingException = Nothing ' Important -- See text 
    Catch Ex As Exception When CopyFirstArgumentToSecondAndReturnFalse(Ex, PendingException) 
    Throw ' Should never happen if above function returns false like it should 
    Finally 
    ' PendingException will be Nothing if Try completed normally, or hold exception if not. 
    Try 
     .. Cleanup 
    Catch Ex as Exception 
     Throw New FailedCleanupException(Ex, PendingException) 
    End Try 
    End Try 

Beachten Sie, dass dies eine sehr nützliche Technik sein kann, wenn Code, der durch eine Ausnahme ausgelöst werden würde, wird garantiert bis Ende entweder Erneutes Auslösen der Ausnahme oder eine neue Aggregat Ausnahme werfen. Wenn die Ausnahme schließlich nicht behandelt wird, wird der Debugger-Trap "Unhandled Exception" ausgelöst, wenn die ursprüngliche Ausnahme auftritt und nicht beim letzten Mal. Dies kann das Debuggen beträchtlich erleichtern, da dem Debugger eine Menge von Programmstatus zur Verfügung steht, der ansonsten zerstört würde.

Beachten Sie auch, dass die PendingException am Ende des Try-Hauptblocks explizit gelöscht wird. Es ist möglich, dass PendingException einen Wert hat, auch wenn keine ausstehende Ausnahme vorliegt. Dies kann auftreten, wenn etwas in einem doppelt verschachtelten Try-Block eine Ausnahme auslöst, die nicht in unserem try-Block gefangen wird, aber die Finally-Klausel des internen Try-Blocks eine Ausnahme auslöst, die innerhalb des einfach verschachtelten Try-Blocks gefangen wird. In diesem Fall verschwindet die ursprüngliche Ausnahme effektiv. Es kann sinnvoll sein, einen speziellen Protokolleintrag zu generieren, wenn entweder "CopyFirstParameterToSecondAndReturnFalse" oder "PendingException = Nothing" ausgeführt wird, wenn PendingException ungleich null ist, da dieses Szenario wahrscheinlich einen Fehler darstellt, der nirgendwo sonst protokolliert werden kann. Wenn jedoch mehrere verschachtelte Catch-Blöcke vorhanden sind, können sie redundante Protokolleinträge generieren.

Da C# die für diesen Ansatz erforderliche Ausnahmefilterung nicht unterstützt, kann es hilfreich sein, einen VB-Wrapper zu schreiben, der C# -Delegaten aufrufen kann, jedoch die erforderliche Ausnahmebehandlungslogik bereitstellt.

EDIT Es ist nicht möglich, eine Rendite Rendite innerhalb eines Try-Catch-Block zu tun, aber ich würde nicht sehen, dass ein Problem mit so etwas wie verursachen würde:

 
{ 
    Exception ex = null; 

    try 
    { 
    CaptureExceptionInfoButDontCatch(ex,{ 
     /* Stuff that might throw */ 
    }); 
    yield return whatever; 
    CaptureExceptionInfoButDontCatch(ex,{ 
     /* More that might throw */ 
    }); 
    } 
    finally 
    { 
    /* If ex is nothing, exception occurred */ 
    } 
} 
+0

Danke. Es gibt nur einen Haken: Selbst wenn C# die VB-Methode verwenden könnte, liegt das Problem darin, dass Iteratoren (speziell das Schlüsselwort _yield_) nicht erlauben, dass "catch" überhaupt verwendet wird. Und aufgrund der verzögerten Natur der Iteratoren wird das Wrapping auch nicht funktionieren; Der Wrapper würde einfach einen Verweis auf [eine Instanz von] der vom Compiler generierten Iterator-Klasse zurückgeben, und wenn der Code tatsächlich ausgeführt wird, ist try/catch bereits außerhalb des Gültigkeitsbereichs ... – KristoferA

+1

Die Renditerückgabe erlaubt keine "Rendite" innerhalb von Ein try-catch-Block, aber das sollte nicht verhindern, dass der Code oberhalb und unterhalb der Rendite in Blöcken verpackt wird, die die Ausnahme erfassen, ohne sie zu fangen. Siehe oben Bearbeiten. – supercat

Verwandte Themen