Определите, выполняется ли выполнение в конечном итоге блока из-за исключения
Можно ли определить, выполняется ли код в настоящее время в контексте обработчика finally
в результате генерирования исключения? Я предпочитаю использовать шаблон IDisposable
для реализации функциональных возможностей ввода/вывода, но одна проблема с этим шаблоном заключается в том, что вы, возможно, не обязательно хотите, чтобы поведение в конце области видимости происходило, если исключение происходит в теле using
. Я бы искал что-то вроде этого:
public static class MyClass
{
public static void MyMethod()
{
using (var scope = MyScopedBehavior.Begin())
{
//Do stuff with scope here
}
}
}
public sealed class MyScopedBehavior : IDisposable
{
private MyScopedBehavior()
{
//Start of scope behavior
}
public void Dispose()
{
//I only want to execute the following if we're not unwinding
//through finally due to an exception:
//...End of scope behavior
}
public static MyScopedBehavior Begin()
{
return new MyScopedBehavior();
}
}
Есть и другие способы, которыми я могу это выполнить (передайте делегат функции, которая окружает вызов с определенным поведением), но мне любопытно, можно ли это сделать с помощью шаблона IDisposable
.
На самом деле это, по-видимому, было спрошено и ответили до здесь. Это можно обнаружить очень хаотичным способом. Я бы не использовал эту технику, но мне интересно знать, что это возможно.
Ответы
Ответ 1
Средство выполнения этого, которое я видел, требует дополнительного метода:
public static void MyMethod()
{
using (var scope = MyScopedBehavior.Begin())
{
//Do stuff with scope here
scope.Complete(); // Tells the scope that it good
}
}
Таким образом, объект области видимости может отслеживать, удаляет ли он из-за ошибки или успешной операции. Это подход, используемый TransactionScope, например (см. TransactionScope.Complete).
Ответ 2
В качестве бокового пункта IL позволяет указать блоки SEH fault
, которые похожи на finally
, но вводятся только при вызове исключения - вы можете увидеть пример здесь, около 2/3rds вниз по странице. К сожалению, С# не раскрывает эту функциональность.
Ответ 3
Я искал что-то похожее для модульного тестирования. У меня есть вспомогательный класс, который я использую для очистки объектов после тестового прогона, и я хочу сохранить хороший, чистый синтаксис "using". Я также хотел, чтобы не очистка, если тест не удался. То, что я придумал, - это вызвать Marshal.GetExceptionCode(). Я не знаю, подходит ли это для всех случаев, но для тестового кода он работает нормально.
Ответ 4
Лучшее, что я могу придумать, будет:
using (var scope = MyScopedBehavior.Begin())
{
try
{
//Do stuff with scope here
}
catch(Exception)
{
scope.Cancel();
throw;
}
}
Конечно, scope.Cancel()
гарантирует, что ничего не произойдет в Dispose()
Ответ 5
Следующий шаблон избегает проблемы с неправильным использованием API, то есть метод завершения области, который не называется i.e, полностью опущен или не вызван из-за логического состояния. Я думаю, что это более точно отвечает на ваш вопрос и даже меньше кода для пользователя API.
Edit
Еще более прямо после комментария Дэн:
public class Bling
{
public static void DoBling()
{
MyScopedBehavior.Begin(() =>
{
//Do something.
}) ;
}
}
public static class MyScopedBehavior
{
public static void Begin(Action action)
{
try
{
action();
//Do additonal scoped stuff as there is no exception.
}
catch (Exception ex)
{
//Clean up...
throw;
}
}
}
Ответ 6
Было бы (IMHO очень) полезно, если бы существовал вариант IDisposable
, метод Dispose
которого принимал параметр, чтобы указать, какое исключение, если оно было, было отложено, когда оно было запущено. Среди прочего, в случае, если Dispose
не может выполнить ожидаемую очистку, он сможет выдать исключение, которое включает информацию о более раннем исключении. Это также позволит методу Dispose
генерировать исключение, если код "забывает" делать что-то, что он должен был делать в блоке using
, но не перезаписывать никакое другое исключение, которое может привести к тому, что блок использования будет преждевременно покидать блок. К сожалению, такой функции пока нет.
Существует множество статей, которые предлагают средства использования функций API, чтобы выяснить, существует ли ожидающее исключение. Одна из основных проблем с такими подходами заключается в том, что возможно, что код может выполняться в блоке finally
для try
, который успешно завершен, но может быть вложен в блок finally
, чей try
вышел преждевременно. Даже если метод Dispose
мог идентифицировать, что такая ситуация существует, у него не было бы способа узнать, к какому try
блокировать его "принадлежит". Можно сформулировать примеры, в которых применяется любая из этих ситуаций.
Как бы то ни было, лучший подход, вероятно, должен иметь явный "успешный" метод и предполагать отказ, если он не вызван, и понять, что последствия забывания вызвать метод "успех" должны быть очевидными, даже если исключение не является выброшены. Одна вещь, которая может быть полезной в качестве простого метода утилиты, будет похожа на
T Success<T>(T returnValue)
{
Success();
return T;
}
что позволяет использовать такой код:
return scopeGuard.Success(thingThatMightThrow());
а не
var result = thingThatMightThrow();
scopeGuard.Success();
return result;
Ответ 7
Я думаю, что лучший способ - написать запись try/catch/finally
вручную. Изучите элемент из первой книги "Эффективный С#". Хороший хакер С# должен точно знать, что использует расширение. Он немного изменился с момента .Net 1.1 - теперь вы можете использовать несколько, используя один под другим. Поэтому используйте рефлектор и изучите незатвержденный код.
Затем, когда вы пишете свой собственный код - либо используйте using
, либо напишите свой собственный материал. Это не очень сложно, и хорошо знать.
Вы могли бы полюбить другие трюки, но он чувствует себя слишком тяжелым и даже неэффективным. Позвольте мне включить образец кода.
LAZY WAY:
using (SqlConnection cn = new SqlConnection(connectionString))
using (SqlCommand cm = new SqlCommand(commandString, cn))
{
cn.Open();
cm.ExecuteNonQuery();
}
РУЧНОЙ ПУТЬ:
bool sawMyEx = false;
SqlConnection cn = null;
SqlCommand cm = null;
try
{
cn = new SqlConnection(connectionString);
cm = new SqlCommand(commandString, cn);
cn.Open();
cm.ExecuteNonQuery();
}
catch (MyException myEx)
{
sawMyEx = true; // I better not tell my wife.
// Do some stuff here maybe?
}
finally
{
if (sawMyEx)
{
// Piss my pants.
}
if (null != cm);
{
cm.Dispose();
}
if (null != cn)
{
cn.Dispose();
}
}
Ответ 8
Почему бы просто не удалять изнутри блок try { }
в самом конце и вообще не использовать окончательно? Это похоже на поведение, которое вы ищете.
Это также кажется более реалистичным с точки зрения того, как другие могут использовать ваш класс. Вы уверены, что все, кто когда-либо использует его, никогда не захотят распоряжаться в случае исключения? Или это поведение должно обрабатываться потребителем класса?