it-swarm.com.de

Was ist der richtige Weg, um eine Ausnahme in C # erneut auszulösen?

Ich habe eine Frage an Sie, die sich daraus ergibt, dass mein Partner Dinge anders macht als ich.

Ist es besser, dies zu tun:

try
{
    ...
}
catch (Exception ex)
{
    ...
    throw;
}

oder dieses:

try
{
    ...
}
catch (Exception ex)
{
    ...
    throw ex;
}

Tun sie dasselbe? Ist einer besser als der andere?

427

Sie sollten immer die folgende Syntax verwenden, um eine Ausnahme erneut auszulösen. Andernfalls wird der Stack-Trace gestoppt:

throw;

Wenn Sie den Trace drucken, der sich aus "throw ex" ergibt, werden Sie feststellen, dass er an dieser Anweisung endet und nicht an der eigentlichen Quelle der Ausnahme.

Grundsätzlich sollte die Verwendung von "throw ex" als Straftat angesehen werden.

749

Meine Vorlieben sind zu verwenden

try 
{
}
catch (Exception ex)
{
     ...
     throw new Exception ("Put more context here", ex)
}

Dadurch bleibt der ursprüngliche Fehler erhalten, Sie können jedoch mehr Kontext einfügen, z. B. eine Objekt-ID, eine Verbindungszeichenfolge oder ähnliches. Häufig müssen in meinem Ausnahmemeldungstool 5 verkettete Ausnahmen gemeldet werden, von denen jede mehr Details enthält.

152
RB.

Wenn Sie eine Ausnahme mit out einer Variablen auslösen (das zweite Beispiel), enthält StackTrace die ursprüngliche Methode, die die Ausnahme ausgelöst hat.

Im ersten Beispiel wird StackTrace geändert, um die aktuelle Methode widerzuspiegeln.

Beispiel:

static string ReadAFile(string fileName) {
    string result = string.Empty;
    try {
        result = File.ReadAllLines(fileName);
    } catch(Exception ex) {
        throw ex; // This will show ReadAFile in the StackTrace
        throw;    // This will show ReadAllLines in the StackTrace
    }
36
Bobby Z

Die erste behält die ursprüngliche Stapelspur der Ausnahme bei, die zweite ersetzt sie durch die aktuelle Position.

Daher ist der erste mit Abstand der bessere.

22
Quibblesome

Ich weiß, dass dies eine alte Frage ist, aber ich werde sie beantworten, weil ich hier mit allen Antworten nicht einverstanden sein muss.

Jetzt stimme ich zu, dass Sie die meiste Zeit entweder ein einfaches throw ausführen möchten, um so viele Informationen wie möglich darüber zu erhalten, was schief gelaufen ist, oder Sie möchten eine neue Ausnahme auslösen, die möglicherweise Folgendes enthält: eine innere Ausnahme oder nicht, je nachdem, wie wahrscheinlich es ist, dass Sie etwas über die inneren Ereignisse wissen möchten, die sie verursacht haben.

Es gibt jedoch eine Ausnahme. Es gibt mehrere Fälle, in denen eine Methode eine andere Methode aufruft, und eine Bedingung, die im inneren Aufruf eine Ausnahme verursacht, sollte im äußeren Aufruf als dieselbe Ausnahme betrachtet werden.

Ein Beispiel ist eine spezialisierte Sammlung, die mithilfe einer anderen Sammlung implementiert wird. Angenommen, es ist ein DistinctList<T>, Der einen List<T> Einschließt, aber doppelte Elemente ablehnt.

Wenn jemand in Ihrer Auflistungsklasse ICollection<T>.CopyTo Aufgerufen hat, ist dies möglicherweise nur ein direkter Aufruf von CopyTo in der inneren Auflistung (wenn beispielsweise die gesamte benutzerdefinierte Logik nur auf das Hinzufügen zur Auflistung oder Einstellung angewendet wird) es auf). Die Bedingungen, unter denen dieser Aufruf ausgelöst wird, sind genau die Bedingungen, unter denen Ihre Sammlung ausgelöst werden sollte, um mit der Dokumentation von ICollection<T>.CopyTo Übereinzustimmen.

Jetzt konnte man die Hinrichtung einfach gar nicht fassen und durchlassen. Hier erhält der Benutzer jedoch eine Ausnahme von List<T>, Wenn er etwas auf einem DistinctList<T> Aufruft. Nicht das Ende der Welt, aber möglicherweise möchten Sie diese Implementierungsdetails ausblenden.

Oder Sie können Ihre eigenen Überprüfungen durchführen:

public CopyTo(T[] array, int arrayIndex)
{
  if(array == null)
    throw new ArgumentNullException("array");
  if(arrayIndex < 0)
    throw new ArgumentOutOfRangeException("arrayIndex", "Array Index must be zero or greater.");
  if(Count > array.Length + arrayIndex)
    throw new ArgumentException("Not enough room in array to copy elements starting at index given.");
  _innerList.CopyTo(array, arrayIndex);
}

Das ist nicht der schlechtere Code, da es sich um Boilerplate handelt und wir ihn wahrscheinlich einfach von einer anderen Implementierung von CopyTo kopieren können, bei der es sich nicht um eine einfache Weitergabe handelte und wir ihn selbst implementieren mussten. Trotzdem werden die exakt das gleiche Prüfungen, die in _innerList.CopyTo(array, arrayIndex) durchgeführt werden, unnötig wiederholt. Das einzige, was unserem Code hinzugefügt wurde, sind 6 Zeilen, in denen es einen Fehler geben könnte .

Wir könnten überprüfen und verpacken:

public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentNullException ane)
  {
    throw new ArgumentNullException("array", ane);
  }
  catch(ArgumentOutOfRangeException aore)
  {
    throw new ArgumentOutOfRangeException("Array Index must be zero or greater.", aore);
  }
  catch(ArgumentException ae)
  {
    throw new ArgumentException("Not enough room in array to copy elements starting at index given.", ae);
  }
}

In Bezug auf neuen Code, der möglicherweise fehlerhaft sein könnte, ist dies sogar noch schlimmer. Und wir gewinnen nichts aus den inneren Ausnahmen. Wenn wir dieser Methode ein Null-Array übergeben und ein ArgumentNullException erhalten, lernen wir nichts, indem wir die innere Ausnahme untersuchen und erfahren, dass ein Aufruf von _innerList.CopyTo Einem Null-Array übergeben wurde und warf ein ArgumentNullException.

Hier können wir alles tun, was wir wollen:

public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentException ae)
  {
    throw ae;
  }
}

Jede der Ausnahmen, die wir zu erwarten haben, wenn der Benutzer sie mit falschen Argumenten aufruft, wird durch diesen erneuten Wurf korrekt ausgelöst. Wenn es einen Fehler in der hier verwendeten Logik gibt, handelt es sich um eine von zwei Zeilen - entweder waren wir falsch, als wir entschieden haben, dass dies ein Fall ist, in dem dieser Ansatz funktioniert, oder wir hatten falsch, dass ArgumentException als Ausnahmetyp gesucht wurde . Es sind die einzigen zwei Fehler, die der catch-Block möglicherweise haben kann.

Jetzt. Ich stimme weiterhin zu, dass Sie die meiste Zeit entweder eine einfache throw; Oder eine eigene Ausnahme konstruieren möchten, um das Problem aus der Sicht der fraglichen Methode direkter zu lösen. Es gibt Fälle wie die oben genannten, in denen ein erneutes Werfen sinnvoller ist, und es gibt viele andere Fälle. Z.B. Ein ganz anderes Beispiel: Wenn ein mit einem FileStream und einem XmlTextReader implementierter ATOM - Dateireader einen Dateifehler oder ungültiges XML empfängt, wird dies möglicherweise der Fall sein Ich möchte genau dieselbe Ausnahme auslösen, die es von diesen Klassen erhalten hat, aber es sollte für den Aufrufer so aussehen, dass es AtomFileReader ist, der ein FileNotFoundException oder XmlException auslöst, also könnten sie Kandidaten für ein ähnliches Nachwerfen sein.

Bearbeiten:

Wir können auch beides kombinieren:

public CopyTo(T[] array, int arrayIndex)
{
  try
  {
    _innerList.CopyTo(array, arrayIndex);
  }
  catch(ArgumentException ae)
  {
    throw ae;
  }
  catch(Exception ex)
  {
    //we weren't expecting this, there must be a bug in our code that put
    //us into an invalid state, and subsequently let this exception happen.
    LogException(ex);
    throw;
  }
}
17
Jon Hanna

Sie sollten immer "throw" verwenden. die Ausnahmen in .NET neu zu werfen,

Lesen Sie hierzu http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

Grundsätzlich hat MSIL (CIL) zwei Befehle - "throw" und "rethrow" und C # 's "throw ex"; wird in MSIL's "throw" und C # 's "throw" kompiliert; - in MSIL "umwerfen"! Grundsätzlich kann ich den Grund sehen, warum "throw ex" den Stack-Trace überschreibt.

8
Vinod T. Patil

Das erste ist besser. Wenn Sie versuchen, die zweite zu debuggen und den Aufrufstapel zu betrachten, werden Sie nicht sehen, woher die ursprüngliche Ausnahme kam. Es gibt Tricks, um den Call-Stack intakt zu halten (versuchen Sie es mit der Suche, die bereits beantwortet wurde), wenn Sie wirklich einen Neuwurf durchführen müssen.

5
Mendelt

Es hängt davon ab, ob. In einem Debug-Build möchte ich den ursprünglichen Stack-Trace mit möglichst geringem Aufwand anzeigen. In diesem Fall "werfen"; passt die Rechnung. In einem Release-Build möchte ich jedoch (a) den Fehler mit dem eingebundenen ursprünglichen Stack-Trace protokollieren und (b) die Fehlerbehandlung neu gestalten, um dem Benutzer mehr Sinn zu machen. Hier macht "Throw Exception" Sinn. Das erneute Auslösen des Fehlers verwirft zwar den ursprünglichen Stack-Trace, ein Nicht-Entwickler kann jedoch die Stack-Trace-Informationen nicht sehen, sodass es in Ordnung ist, den Fehler erneut auszulösen.

        void TrySuspectMethod()
        {
            try
            {
                SuspectMethod();
            }
#if DEBUG
            catch
            {
                //Don't log error, let developer see 
                //original stack trace easily
                throw;
#else
            catch (Exception ex)
            {
                //Log error for developers and then 
                //throw a error with a user-oriented message
                throw new Exception(String.Format
                    ("Dear user, sorry but: {0}", ex.Message));
#endif
            }
        }

Die Art und Weise, in der die Frage formuliert ist: "Throw:" vs. "Throw ex;" macht es ein bisschen wie ein roter Hering. Die wirkliche Wahl ist zwischen "Werfen"; und "Throw Exception", wobei "Throw Ex"; ist ein unwahrscheinlicher Sonderfall von "Throw Exception".

3
Perry Tribolet

Ich fand heraus, dass, wenn die Ausnahme in der gleichen Methode ausgelöst wird, in der sie abgefangen wird, die Stapelablaufverfolgung nicht beibehalten wird, was es wert ist.

void testExceptionHandling()
{
    try
    {
        throw new ArithmeticException("illegal expression");
    }
    catch (Exception ex)
    {
        throw;
    }
    finally
    {
        System.Diagnostics.Debug.WriteLine("finally called.");
    }
}
3
James