Якщо нарешті блок кидає виняток, що саме відбувається?
Зокрема, що станеться, якщо виняток буде перекинуто на середину через остаточний блок. Чи викликаються решта тверджень (після) у цьому блоці?
Я знаю, що винятки поширюватимуться вгору.
Якщо нарешті блок кидає виняток, що саме відбувається?
Зокрема, що станеться, якщо виняток буде перекинуто на середину через остаточний блок. Чи викликаються решта тверджень (після) у цьому блоці?
Я знаю, що винятки поширюватимуться вгору.
Відповіді:
Якщо нарешті блок кидає виняток, що саме відбувається?
Цей виняток поширюється і збільшується, і його можна (можна) обробляти на більш високому рівні.
Ваш остаточний блок не буде завершений поза межею, де викинуто виняток.
Якщо остаточний блок був виконаний під час обробки попереднього винятку, то перший виняток втрачається.
Спеціалізація мови C # 4 § 8.9.5: Якщо остаточний блок кидає інший виняток, обробка поточного винятку припиняється.
ThreadAbortException
, тоді весь блок нарешті буде закінчений спочатку, оскільки це критичний розділ.
Для таких питань, як правило, я відкриваю проект пустої програми консолі у Visual Studio і пишу невелику зразок програми:
using System;
class Program
{
static void Main(string[] args)
{
try
{
try
{
throw new Exception("exception thrown from try block");
}
catch (Exception ex)
{
Console.WriteLine("Inner catch block handling {0}.", ex.Message);
throw;
}
finally
{
Console.WriteLine("Inner finally block");
throw new Exception("exception thrown from finally block");
Console.WriteLine("This line is never reached");
}
}
catch (Exception ex)
{
Console.WriteLine("Outer catch block handling {0}.", ex.Message);
}
finally
{
Console.WriteLine("Outer finally block");
}
}
}
Запустивши програму, ви побачите точний порядок виконання catch
та finally
блоків. Зверніть увагу, що код в остаточному блоці після скидання винятку не буде виконуватися (насправді, у цій зразковій програмі Visual Studio навіть попереджатиме вас, що виявив недоступний код):
Виняток обробки внутрішнього блоку лову, викинутий із спробу блоку. Внутрішня нарешті блокується Виняток з керування зовнішнім уловом блоком, викинутий з остаточно блоку. Зовнішній нарешті блокувати
Додаткове зауваження
Як зазначав Михайло Даматов, виняток із try
блоку буде "з'їдений", якщо ви не обробляєте його у внутрішньому catch
блоці. Фактично, у прикладі вище повторно викинутий виняток не відображається у зовнішньому блоці захоплення. Щоб зробити це ще більш чітким, подивіться на наступний трохи модифікований зразок:
using System;
class Program
{
static void Main(string[] args)
{
try
{
try
{
throw new Exception("exception thrown from try block");
}
finally
{
Console.WriteLine("Inner finally block");
throw new Exception("exception thrown from finally block");
Console.WriteLine("This line is never reached");
}
}
catch (Exception ex)
{
Console.WriteLine("Outer catch block handling {0}.", ex.Message);
}
finally
{
Console.WriteLine("Outer finally block");
}
}
}
Як видно з результату, внутрішній виняток "втрачається" (тобто ігнорується):
Внутрішня нарешті блокується Виняток з керування зовнішнім уловом блоком, викинутий з остаточно блоку. Зовнішній нарешті блокувати
finally
блок (майже) завжди буде виконуватися, це також стосується цього випадку для внутрішнього остаточного блоку (просто спробуйте зразкову програму самостійно (Зрештою, блок не буде виконаний у випадку неповернення виняток, наприклад EngineExecutionException
, але в такому випадку ваша програма все одно припиниться)
Якщо є виняток, що очікує на розгляд (коли у try
блоку є finally
но немає catch
), новий виняток замінює цей.
Якщо очікуваний виняток не існує, він працює так само, як викидання виключення за межі finally
блоку.
catch
блок узгодження, який (повторно) кидає виняток.
Швидкий (і досить очевидний) фрагмент, щоб зберегти "оригінальний виняток" (кинутий у try
блок) та пожертвувати "нарешті винятком" (кинутий у finally
блок), якщо оригінальний для вас важливіший:
try
{
throw new Exception("Original Exception");
}
finally
{
try
{
throw new Exception("Finally Exception");
}
catch
{ }
}
Коли код, виконаний вище, "Оригінальний виняток" поширює стек виклику, а "Нарешті виняток" втрачається.
Мені довелося це зробити, щоб виявити помилку, намагаючись закрити потік, який ніколи не відкривали через виняток.
errorMessage = string.Empty;
try
{
byte[] requestBytes = System.Text.Encoding.ASCII.GetBytes(xmlFileContent);
webRequest = WebRequest.Create(url);
webRequest.Method = "POST";
webRequest.ContentType = "text/xml;charset=utf-8";
webRequest.ContentLength = requestBytes.Length;
//send the request
using (var sw = webRequest.GetRequestStream())
{
sw.Write(requestBytes, 0, requestBytes.Length);
}
//get the response
webResponse = webRequest.GetResponse();
using (var sr = new StreamReader(webResponse.GetResponseStream()))
{
returnVal = sr.ReadToEnd();
sr.Close();
}
}
catch (Exception ex)
{
errorMessage = ex.ToString();
}
finally
{
try
{
if (webRequest.GetRequestStream() != null)
webRequest.GetRequestStream().Close();
if (webResponse.GetResponseStream() != null)
webResponse.GetResponseStream().Close();
}
catch (Exception exw)
{
errorMessage = exw.ToString();
}
}
якщо веб-запит був створений, але помилка підключення сталася під час
using (var sw = webRequest.GetRequestStream())
то, нарешті, буде спіймано виняток, який намагається закрити зв’язки, які, на його думку, були відкритими, оскільки створено веб-запит.
Якщо нарешті не було спроби вловлювати всередині, цей код може спричинити необроблений виняток під час очищення webRequest
if (webRequest.GetRequestStream() != null)
звідти код вийшов, не належним чином обробляючи помилку, що сталася, і тому спричиняє проблеми для методу виклику.
Сподіваюсь, це допомагає як приклад
Якщо викинути виняток під час активного іншого винятку, то перший виняток замінить другий (пізніший) виняток.
Ось код, який ілюструє те, що відбувається:
public static void Main(string[] args)
{
try
{
try
{
throw new Exception("first exception");
}
finally
{
//try
{
throw new Exception("second exception");
}
//catch (Exception)
{
//throw;
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
Деякі місяці тому я також стикався з чимось подібним,
private void RaiseException(String errorMessage)
{
throw new Exception(errorMessage);
}
private void DoTaskForFinally()
{
RaiseException("Error for finally");
}
private void DoTaskForCatch()
{
RaiseException("Error for catch");
}
private void DoTaskForTry()
{
RaiseException("Error for try");
}
try
{
/*lacks the exception*/
DoTaskForTry();
}
catch (Exception exception)
{
/*lacks the exception*/
DoTaskForCatch();
}
finally
{
/*the result exception*/
DoTaskForFinally();
}
Для вирішення такої проблеми я зробив клас корисності
class ProcessHandler : Exception
{
private enum ProcessType
{
Try,
Catch,
Finally,
}
private Boolean _hasException;
private Boolean _hasTryException;
private Boolean _hasCatchException;
private Boolean _hasFinnallyException;
public Boolean HasException { get { return _hasException; } }
public Boolean HasTryException { get { return _hasTryException; } }
public Boolean HasCatchException { get { return _hasCatchException; } }
public Boolean HasFinnallyException { get { return _hasFinnallyException; } }
public Dictionary<String, Exception> Exceptions { get; private set; }
public readonly Action TryAction;
public readonly Action CatchAction;
public readonly Action FinallyAction;
public ProcessHandler(Action tryAction = null, Action catchAction = null, Action finallyAction = null)
{
TryAction = tryAction;
CatchAction = catchAction;
FinallyAction = finallyAction;
_hasException = false;
_hasTryException = false;
_hasCatchException = false;
_hasFinnallyException = false;
Exceptions = new Dictionary<string, Exception>();
}
private void Invoke(Action action, ref Boolean isError, ProcessType processType)
{
try
{
action.Invoke();
}
catch (Exception exception)
{
_hasException = true;
isError = true;
Exceptions.Add(processType.ToString(), exception);
}
}
private void InvokeTryAction()
{
if (TryAction == null)
{
return;
}
Invoke(TryAction, ref _hasTryException, ProcessType.Try);
}
private void InvokeCatchAction()
{
if (CatchAction == null)
{
return;
}
Invoke(TryAction, ref _hasCatchException, ProcessType.Catch);
}
private void InvokeFinallyAction()
{
if (FinallyAction == null)
{
return;
}
Invoke(TryAction, ref _hasFinnallyException, ProcessType.Finally);
}
public void InvokeActions()
{
InvokeTryAction();
if (HasTryException)
{
InvokeCatchAction();
}
InvokeFinallyAction();
if (HasException)
{
throw this;
}
}
}
І використовується так
try
{
ProcessHandler handler = new ProcessHandler(DoTaskForTry, DoTaskForCatch, DoTaskForFinally);
handler.InvokeActions();
}
catch (Exception exception)
{
var processError = exception as ProcessHandler;
/*this exception contains all exceptions*/
throw new Exception("Error to Process Actions", exception);
}
але якщо ви хочете використовувати параметри та типи повернення, це вже інша історія
public void MyMethod()
{
try
{
}
catch{}
finally
{
CodeA
}
CodeB
}
Спосіб обробки винятків, викинутих CodeA та CodeB, однаковий.
Виняток, кинутий у finally
блок, не має нічого особливого, трактуйте це як викид для винятку за кодом B.
Виняток поширюється вгору, і його слід обробляти на більш високому рівні. Якщо виняток не обробляється на більш високому рівні, програма виходить з ладу. Виконання блоку "нарешті" зупиняється на тому місці, де викидається виняток.
Незалежно від того, є виняток чи ні, "нарешті" блок гарантовано виконується.
Якщо блок "нарешті" виконується після винятку в блоці спробу,
і якщо цей виняток не обробляється
і якщо нарешті блок кидає виняток
Тоді початковий виняток, що стався у блоці спробу, втрачається.
public class Exception
{
public static void Main()
{
try
{
SomeMethod();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
public static void SomeMethod()
{
try
{
// This exception will be lost
throw new Exception("Exception in try block");
}
finally
{
throw new Exception("Exception in finally block");
}
}
}