Po co łapać i zmieniać wyjątek w C#?

Patrzę na Artykuł C# - Obiekt Transmisji Danych na serwerze dtos.

Artykuł zawiera ten fragment kodu:

public static string SerializeDTO(DTO dto) {
    try {
        XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
        StringWriter sWriter = new StringWriter();
        xmlSer.Serialize(sWriter, dto);
        return sWriter.ToString();
    }
    catch(Exception ex) {
        throw ex;
    }
}

Reszta artykułu wygląda na zdrową i rozsądną (dla nooba), ale to try-catch-throw rzuca WtfException... czy to nie jest równoznaczne z brakiem obsługi wyjątków?

Ergo:

public static string SerializeDTO(DTO dto) {
    XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
    StringWriter sWriter = new StringWriter();
    xmlSer.Serialize(sWriter, dto);
    return sWriter.ToString();
}

A może brakuje mi czegoś fundamentalnego w obsłudze błędów w C#? To prawie to samo co Java (minus zaznaczone wyjątki), prawda? ... Oznacza to, że obaj udoskonalili C++.

Pytanie o przepełnienie stosu różnica między re-throwing parameter-less catch a not doing anything? wydaje się potwierdzać moje twierdzenie, że try-catch-throw jest-no-op.


EDIT:

Dla podsumowania dla każdego, kto znajdzie ten wątek w przyszłości...

NIE

try {
    // Do stuff that might throw an exception
}
catch (Exception e) {
    throw e; // This destroys the strack trace information!
}

Informacje o śledzeniu stosu mogą być kluczowe dla identyfikacji główna przyczyna problemu!

DO

try {
    // Do stuff that might throw an exception
}
catch (SqlException e) {
    // Log it
    if (e.ErrorCode != NO_ROW_ERROR) { // filter out NoDataFound.
        // Do special cleanup, like maybe closing the "dirty" database connection.
        throw; // This preserves the stack trace
    }
}
catch (IOException e) {
    // Log it
    throw;
}
catch (Exception e) {
    // Log it
    throw new DAOException("Excrement occurred", e); // wrapped & chained exceptions (just like java).
}
finally {
    // Normal clean goes here (like closing open files).
}

Łap bardziej konkretne wyjątki przed mniej szczegółowymi (tak jak Java).


Referencje:

Author: Community, 2009-05-19

16 answers

Po Pierwsze; sposób, w jaki kod w artykule to robi, jest zły. throw ex zresetuje stos wywołań w wyjątku do punktu, w którym znajduje się ta instrukcja throw; traci informacje o tym, gdzie wyjątek został utworzony.

Po drugie, jeśli po prostu łapiesz i rzucasz w ten sposób, nie widzę żadnej wartości dodanej, powyższy przykład kodu byłby równie dobry (lub, biorąc pod uwagę bit throw ex, nawet lepszy) bez try-catch.

Są jednak przypadki, w których możesz chcieć złapać i rethrow wyjątek. Logowanie może być jednym z nich:

try 
{
    // code that may throw exceptions    
}
catch(Exception ex) 
{
    // add error logging here
    throw;
}
 361
Author: Fredrik Mörk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-19 08:06:23

Nie rób tego,

try 
{
...
}
catch(Exception ex)
{
   throw ex;
}
Stracisz informacje o śledzeniu stosu...

Albo zrobić,

try { ... }
catch { throw; }

Lub

try { ... }
catch (Exception ex)
{
    throw new Exception("My Custom Error Message", ex);
}

Jednym z powodów, dla których możesz chcieć zmienić zdanie, jest to, że zajmujesz się różnymi wyjątkami, dla np.

try
{
   ...
}
catch(SQLException sex)
{
   //Do Custom Logging 
   //Don't throw exception - swallow it here
}
catch(OtherException oex)
{
   //Do something else
   throw new WrappedException("Other Exception occured");
}
catch
{
   System.Diagnostics.Debug.WriteLine("Eeep! an error, not to worry, will be handled higher up the call stack");
   throw; //Chuck everything else back up the stack
}
 98
Author: Eoin Campbell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-13 14:13:31

C# (przed C# 6) nie obsługuje CIL "filtrowanych WYJĄTKÓW", co robi VB, więc w C# 1-5 jednym z powodów ponownego rzucenia wyjątku jest to, że nie masz wystarczającej ilości informacji w momencie catch (), aby określić, czy chcesz rzeczywiście złapać wyjątek.

Na przykład w VB możesz zrobić

Try
 ..
Catch Ex As MyException When Ex.ErrorCode = 123
 .. 
End Try

...które nie obsługują MyExceptions z różnymi wartościami ErrorCode. W C# przed v6, trzeba by złapać i ponownie rzucić MyException jeśli ErrorCode nie był 123:

try 
{
   ...
}
catch(MyException ex)
{
    if (ex.ErrorCode != 123) throw;
    ...
}

Od C # 6.0 można filtrować tak jak z VB:

try 
{
  // Do stuff
} 
catch (Exception e) when (e.ErrorCode == 123456) // filter
{
  // Handle, other exceptions will be left alone and bubble up
}
 48
Author: bzlm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-18 03:55:17

Mój główny powód posiadania kodu typu:

try
{
    //Some code
}
catch (Exception e)
{
    throw;
}

Jest więc mogę mieć punkt przerwania w catch, który ma instancyjny obiekt wyjątku. Robię to często podczas programowania/debugowania. Oczywiście kompilator daje mi ostrzeżenie na temat wszystkich nieużywanych e-maili, a najlepiej powinny być usunięte przed kompilacją release.

Są ładne podczas debugowania.

 12
Author: Peter Mortensen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-09 14:28:42

Ważnym powodem zmiany wyjątków może być to, że chcesz dodać informacje do wyjątku, lub może zawinąć oryginalny wyjątek w jeden z twoich własnych projektów:

public static string SerializeDTO(DTO dto) {
  try {
      XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
      StringWriter sWriter = new StringWriter();
      xmlSer.Serialize(sWriter, dto);
      return sWriter.ToString();
  }
  catch(Exception ex) {
    string message = 
      String.Format("Something went wrong serializing DTO {0}", DTO);
    throw new MyLibraryException(message, ex);
  }
}
 10
Author: edosoft,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-19 08:45:19

Czy to nie jest dokładnie równoważne nie obsługa wyjątków w ogóle?

Nie do końca, to nie to samo. Resetuje stacktrace wyjątku. Choć zgadzam się, że to chyba pomyłka, a co za tym idzie przykład złego kodu.
 9
Author: Arjan Einbu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-19 08:11:11

Nie chcesz rzucać ex - bo to straci stos połączeń. Zobacz też Obsługa wyjątków (MSDN).

I tak, próba...catch nie robi nic pożytecznego (poza utratą stosu połączeń-więc faktycznie jest gorzej-chyba że z jakiegoś powodu nie chciałeś ujawnić tej informacji).

 7
Author: Duncan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-09 14:26:09

Kwestia, o której ludzie nie wspominali, jest taka, że chociaż języki.NET tak naprawdę nie rozróżniają, to pytanie, czy należy podjąć działanie gdy wystąpi wyjątek, i czy ktoś rozwiąże to, są w rzeczywistości odrębnymi pytaniami. Istnieje wiele przypadków, w których należy podjąć działania w oparciu o wyjątki, które nie mają nadziei na rozwiązanie, a są przypadki, w których wszystko, co jest konieczne do" rozwiązania " wyjątku, to odwinięcie stosu do pewnego uwaga-nie wymaga dalszych działań.

Ze względu na powszechną mądrość, że należy tylko "łapać" rzeczy, które można "obsługiwać", dużo kodu, który powinien działać, gdy występują wyjątki, nie ma. na przykład, dużo kodu uzyska blokadę, umieści strzeżony obiekt" tymczasowo " w stanie, który narusza jego niezmienniki, a następnie umieści go w stanie zgodnym z prawem, a następnie zwolni blokadę, zanim ktokolwiek inny będzie mógł zobaczyć obiekt. Jeśli wystąpi wyjątek, gdy obiekt znajduje się w niebezpiecznie-stan nieprawidłowy, powszechną praktyką jest zwolnienie blokady z obiektem wciąż w tym stanie. Znacznie lepszym wzorcem byłoby posiadanie wyjątku, który występuje, gdy obiekt jest w" niebezpiecznym " stanie, wyraźnie unieważnia zamek, więc każda przyszła próba jego zdobycia natychmiast się nie powiedzie. Konsekwentne stosowanie takiego wzorca znacznie poprawiłoby bezpieczeństwo tzw. obsługi wyjątków "Pokemon", które IMHO zyskują złą reputację przede wszystkim ze względu na kod pozwalający na wyjątki do perkolacji bez podejmowania odpowiednich działań.

W większości języków. NET, jedynym sposobem na podjęcie działania w oparciu o wyjątek jest catch (nawet jeśli wie, że nie rozwiąże wyjątku), wykonanie danej akcji, a następnie ponowne-throw). Innym możliwym podejściem, jeśli kod nie obchodzi, jaki wyjątek jest wyrzucany, jest użycie znacznika ok z blokiem try/finally; ustaw znacznik ok na false przed blokiem i na true przed zakończeniem bloku, oraz przed jakąkolwiek return, która znajduje się w bloku. Następnie, w obrębie finally, Załóżmy, że jeśli {[2] }nie jest ustawione, musiał wystąpić wyjątek. Takie podejście jest semantycznie lepsze niż catch/throw, ale jest brzydki i jest mniej konserwowalny niż powinien być.

 5
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-14 20:16:51

Jednym z możliwych powodów do catch-throw jest wyłączenie filtrów WYJĄTKÓW głębiej w górę stosu od filtrowania w dół (RANDOM old link). Ale oczywiście, gdyby taka była intencja, byłby tam komentarz mówiący o tym.

 3
Author: Brian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-30 15:12:20

To zależy, co robisz w bloku catch i czy chcesz przekazać błąd do kodu wywołującego, czy nie.

Możesz powiedzieć Catch io.FileNotFoundExeption ex, a następnie użyć alternatywnej ścieżki do pliku lub czegoś takiego, ale nadal włącza się błąd.

Również wykonywanie Throw zamiast Throw Ex pozwala zachować pełny ślad stosu. Throw ex uruchamia ponownie ślad stosu z instrukcji throw (mam nadzieję, że ma to sens).

 3
Author: Pondidum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-09 14:27:36

Podczas gdy wiele innych odpowiedzi dostarcza dobrych przykładów, dlaczego możesz chcieć złapać wyjątek rethrow, nikt chyba nie wspomniał o scenariuszu "w końcu".

Przykładem tego jest metoda, w której ustawiasz kursor (na przykład kursor oczekiwania), metoda ma kilka punktów wyjścia (np. if () return;) i chcesz się upewnić, że kursor zostanie zresetowany na końcu metody.

Aby to zrobić, możesz zawinąć cały kod w try / catch / finally. W na koniec ustaw kursor z powrotem na prawy kursor. Żeby nie zakopać żadnych ważnych WYJĄTKÓW, wrzucić je do połowu.

try
{
    Cursor.Current = Cursors.WaitCursor;
    // Test something
    if (testResult) return;
    // Do something else
}
catch
{
    throw;
}
finally
{
     Cursor.Current = Cursors.Default;
}
 3
Author: statler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-09 11:47:08

Może to być przydatne, gdy funkcje programowania dla biblioteki lub dll.

Ta struktura rethrowa może być użyta do celowego resetowania stosu wywołań tak, że zamiast zobaczyć wyjątek wyrzucony z pojedynczej funkcji wewnątrz funkcji, otrzymasz wyjątek z samej funkcji.

Myślę, że jest to po prostu używane, aby rzucone wyjątki były czystsze i nie trafiały do "korzeni" biblioteki.

 3
Author: Jackson Tarisa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-27 15:35:30

W przykładzie w kodzie, który napisałeś, w rzeczywistości nie ma sensu łapać wyjątku, ponieważ nie ma nic zrobionego na haczyku, jest on po prostu ponownie thown, w rzeczywistości robi więcej szkody niż pożytku, ponieważ stos wywołań jest stracony.

Możesz jednak złapać wyjątek, aby wykonać jakąś logikę (na przykład zamknięcie połączenia SQL z blokadą plików lub po prostu logowanie) w przypadku wyjątku, wyrzuć go z powrotem do kodu wywołującego, aby sobie z nim poradzić. Byłoby to bardziej powszechne w warstwie biznesowej niż kod przedni, ponieważ możesz chcieć, aby koder implementujący Twoją warstwę biznesową obsłużył wyjątek.

Do ponownej iteracji, choć nie ma sensu łapać wyjątku w opublikowanym przykładzie. Nie rób tego w ten sposób!

 2
Author: Sheff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-19 08:19:11

Przepraszam, ale wiele przykładów jako "ulepszony projekt" nadal śmierdzi okropnie lub może być bardzo mylące. Spróbowanie {} złapać {log; rzucić} jest po prostu zupełnie bezcelowe. Logowanie WYJĄTKÓW powinno odbywać się w centralnym miejscu wewnątrz aplikacji. wyjątki i tak są na stacktrace, czemu nie wpisywać ich gdzieś w górę i blisko granic systemu?

Należy zachować ostrożność podczas serializacji kontekstu (np. DTO w jednym z podanych przykładów) tylko w wiadomości dziennika. Może łatwo zawierać poufne informacje można nie chcieć dotrzeć do rąk wszystkich osób, które mogą uzyskać dostęp do plików dziennika. A jeśli nie dodasz żadnych nowych informacji do wyjątku, naprawdę nie widzę sensu owijania WYJĄTKÓW. Stara dobra Java ma na to jakiś punkt, wymaga od rozmówcy, aby wiedział, jakich wyjątków należy się spodziewać po wywołaniu kodu. Ponieważ nie masz tego w. NET, owijanie nie robi nic dobrego w przynajmniej 80% przypadków, które widziałem.

 2
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-16 07:22:00

Oprócz tego, co powiedzieli inni, zobacz moja odpowiedź na powiązane pytanie, które pokazuje, że catching i rethrowing nie są no-op(jest w VB, ale część kodu może być C# wywoływana z VB).

 1
Author: erikkallen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:38

Większość odpowiedzi mówiących o scenariuszu catch-log-rethrow.

Zamiast pisać go w kodzie rozważ użycie AOP, w szczególności Postsharp.Diagnostyka.Toolkit with OnExceptionOptions IncludeParameterValue and IncludeThisArgument

 1
Author: Michael Freidgeim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-25 22:41:10