Natknąłem się na tę nową funkcję w C #, która pozwala programowi obsługi catch na wykonanie, gdy zostanie spełniony określony warunek.
int i = 0;
try
{
throw new ArgumentNullException(nameof(i));
}
catch (ArgumentNullException e)
when (i == 1)
{
Console.WriteLine("Caught Argument Null Exception");
}
Próbuję zrozumieć, kiedy może się to kiedykolwiek przydać.
Jeden scenariusz może wyglądać mniej więcej tak:
try
{
DatabaseUpdate()
}
catch (SQLException e)
when (driver == "MySQL")
{
//MySQL specific error handling and wrapping up the exception
}
catch (SQLException e)
when (driver == "Oracle")
{
//Oracle specific error handling and wrapping up of exception
}
..
ale jest to znowu coś, co mogę zrobić w ramach tego samego programu obsługi i delegować na różne metody w zależności od typu sterownika. Czy to sprawia, że kod jest łatwiejszy do zrozumienia? Prawdopodobnie nie.
Inny scenariusz, który przychodzi mi do głowy, to coś takiego:
try
{
SomeOperation();
}
catch(SomeException e)
when (Condition == true)
{
//some specific error handling that this layer can handle
}
catch (Exception e) //catchall
{
throw;
}
Znowu to jest coś, co mogę zrobić:
try
{
SomeOperation();
}
catch(SomeException e)
{
if (condition == true)
{
//some specific error handling that this layer can handle
}
else
throw;
}
Czy użycie funkcji „catch, when” przyspiesza obsługę wyjątków, ponieważ program obsługi jest pomijany jako taki, a rozwijanie stosu może nastąpić znacznie wcześniej niż w porównaniu z obsługą określonych przypadków użycia w ramach procedury obsługi? Czy są jakieś konkretne przypadki użycia, które lepiej pasują do tej funkcji i które ludzie mogą następnie przyjąć jako dobrą praktykę?
źródło
when
trzeba uzyskać dostęp do samego wyjątkutry..catch...catch..catch..finally
?catch (Exception ex)
, sprawdzać typ ithrow
inne. Nieco bardziej zorganizowany kod (czyli unikanie szumu kodu) jest dokładnie powodem, dla którego istnieje ta funkcja. (W rzeczywistości jest to prawdą dla wielu funkcji.)Odpowiedzi:
Bloki przechwytywania umożliwiają już filtrowanie według typu wyjątku:
catch (SomeSpecificExceptionType e) {...}
when
Klauzula pozwala przedłużyć ten filtr do wyrażenia generycznych.W związku z tym
when
klauzula jest używana w przypadkach, gdy typ wyjątku nie jest wystarczająco wyraźny, aby określić, czy wyjątek powinien być obsługiwany w tym miejscu, czy nie.Typowym przypadkiem użycia są typy wyjątków, które w rzeczywistości są opakowaniem dla wielu różnych rodzajów błędów.
Oto przypadek, z którego faktycznie korzystałem (w VB, który ma już tę funkcję od dłuższego czasu):
try { SomeLegacyComOperation(); } catch (COMException e) when (e.ErrorCode == 0x1234) { // Handle the *specific* error I was expecting. }
To samo dotyczy
SqlException
, które również maErrorCode
właściwość. Alternatywą byłoby coś takiego:try { SomeLegacyComOperation(); } catch (COMException e) { if (e.ErrorCode == 0x1234) { // Handle error } else { throw; } }
który jest prawdopodobnie mniej elegancki i nieznacznie przerywa ślad stosu .
Ponadto możesz dwukrotnie wspomnieć o tym samym typie wyjątku w tym samym bloku try-catch:
try { SomeLegacyComOperation(); } catch (COMException e) when (e.ErrorCode == 0x1234) { ... } catch (COMException e) when (e.ErrorCode == 0x5678) { ... }
co nie byłoby możliwe bez tego
when
warunku.źródło
catch
, prawda?when
umożliwia wielokrotną obsługę tego samego typu wyjątku. Warto o tym wspomnieć, ponieważ jest to zasadnicza różnica. Bezwhen
dostaniesz błąd kompilatora.Z wiki Roslyn (wyróżnienie moje):
Warto zademonstrować pierwszy punkt.
static class Program { static void Main(string[] args) { A(1); } private static void A(int i) { try { B(i + 1); } catch (Exception ex) { if (ex.Message != "!") Console.WriteLine(ex); else throw; } } private static void B(int i) { throw new Exception("!"); } }
Jeśli uruchomimy to w WinDbg do momentu trafienia wyjątku i wydrukujemy stos używając
!clrstack -i -a
, zobaczymy tylko ramkęA
:Jeśli jednak zmienimy program na używanie
when
:catch (Exception ex) when (ex.Message != "!") { Console.WriteLine(ex); }
Zobaczymy, że stos zawiera również
B
ramkę:Te informacje mogą być bardzo przydatne podczas debugowania zrzutów awaryjnych.
źródło
throw;
(w przeciwieństwie dothrow ex;
) również nie opuści stosu bez szwanku? +1 za efekt uboczny. Nie jestem pewien, czy podoba mi się to, ale dobrze jest wiedzieć o tej technice.throw;
, stos rozwija się i tracisz wartości parametrów.throw;
zmienia to trochę ślad stosu i bardzothrow ex;
go zmienia.throw
nieznacznie zakłóca ślad stosu. Numery linii są różne w przypadku używaniathrow
w przeciwieństwie dowhen
.Gdy wyjątek jest zgłaszany, pierwszy przebieg obsługi wyjątków identyfikuje miejsce, w którym wyjątek zostanie przechwycony przed odwinięciem stosu; jeśli / kiedy zostanie zidentyfikowana lokalizacja „catch”, uruchamiane są wszystkie bloki „final” (należy zauważyć, że jeśli wyjątek ucieka przed blokiem „last”, przetwarzanie wcześniejszego wyjątku może zostać porzucone). Gdy to się stanie, kod wznowi wykonywanie w miejscu „złapania”.
Jeśli w funkcji znajduje się punkt przerwania, który jest oceniany jako część „kiedy”, ten punkt przerwania zawiesi wykonywanie przed jakimkolwiek odwinięciem stosu; W przeciwieństwie do tego, punkt przerwania w „catch” zawiesi wykonywanie dopiero po
finally
uruchomieniu wszystkich programów obsługi.Wreszcie, jeśli linie 23 i 27
foo
wywołaniabar
, a wywołanie w linii 23 zgłasza wyjątek, który zostaje przechwycony wfoo
linii 57 i ponownie zgłoszony, wówczas ślad stosu zasugeruje, że wyjątek wystąpił podczas wywoływaniabar
z linii 57 [lokalizacja ponownego wyrzucenia ] , niszcząc wszelkie informacje o tym, czy wyjątek wystąpił w wywołaniu na linii 23, czy 27. Używanie wwhen
celu uniknięcia wyłapywania wyjątku w pierwszej kolejności pozwala uniknąć takich zakłóceń.BTW, przydatnym wzorcem, który jest irytująco niewygodny zarówno w C #, jak i VB.NET, jest użycie wywołania funkcji w
when
klauzuli w celu ustawienia zmiennej, której można użyć wfinally
klauzuli, aby określić, czy funkcja została ukończona normalnie, aby obsłużyć przypadki, w których funkcja nie ma nadziei na „rozwiązanie” jakiegokolwiek wyjątku, który wystąpił, niemniej jednak musi podjąć działania na jego podstawie. Na przykład, jeśli wyjątek zostanie zgłoszony w ramach metody fabryki, która ma zwrócić obiekt, który hermetyzuje zasoby, wszelkie pozyskane zasoby będą musiały zostać zwolnione, ale bazowy wyjątek powinien przenikać do obiektu wywołującego. Najczystszym sposobem obsługi tego semantycznie (choć nie syntaktycznie) jest posiadanie rozszerzeniafinally
blokuje sprawdzenie, czy wystąpił wyjątek, a jeśli tak, zwolnij wszystkie zasoby pozyskane w imieniu obiektu, który nie będzie już zwracany. Ponieważ kod czyszczący nie ma nadziei na rozwiązanie dowolnego warunku, który spowodował wyjątek, tak naprawdę nie powiniencatch
, a jedynie musi wiedzieć, co się stało. Wywołanie funkcji takiej jak:bool CopySecondArgumentToFirstAndReturnFalse<T>(ref T first, T second) { first = second; return false; }
w
when
klauzuli umożliwi funkcji fabryki wiedzieć, że coś się stało.źródło