#c# #.net
#c# #.net
Вопрос:
Возможно ли определить разницу между обычным выходом using
блока и выходом, вызванным возникновением исключения? Используя try / catch /, наконец, я могу написать:
Foo f = new Foo();
try
{
f.stuff();
}
catch()
{
f.ThereWasAnError();
}
finally
{
f.Dispose();
}
но было бы неплохо, если бы пользователям моего класса не нужно было заботиться об этом, и они могли бы написать
using (var f = new Foo()) { ... }
Я не вижу способа сообщить в методе Dispose, почему меня вызывают. Возможно ли это?
Комментарии:
1. Зачем вам нужно знать? Что вы собираетесь сделать по-другому?
2.
using( var f = newFoo()) { ... f.commit();}
сDispose
блоком, вероятно, сработало бы при условии, что пользователь вызываетcommit
себя. Но я думаю, что это неправильный способ сделать это.
Ответ №1:
Было бы полезно, если бы вы объяснили, что вы пытаетесь сделать. Я подозреваю, что вы пытаетесь сделать что-то вроде этого:
using(t = new Transaction())
{
DoStuff(t);
}
где выполняется транзакция.Dispose(), который вы хотите написать, выглядит следующим образом:
void Dispose()
{
if (I'm being disposed because of an exception)
Roll back the transaction
else
commit the transaction
}
Это злоупотребление одноразовым шаблоном. Цель одноразового шаблона — вежливо освободить неуправляемые ресурсы как можно скорее независимо от того, почему объект удаляется. Это не для реализации семантики транзакции. Если вы хотите иметь семантику транзакции, вам придется написать код самостоятельно; этот шаблон не встроен в язык C #. Способ написать это так:
Transaction t = new Transaction();
try
{
DoStuff(t);
}
catch
{
t.Rollback();
throw;
}
t.Commit();
(Конечно, имея в виду, что может возникнуть исключение прерывания потока, вызванное до фиксации, но после doStuff; если это вас беспокоит, тогда вам нужно будет написать код с ограниченным регионом выполнения.)
Если вы хотите упростить задачу для своих пользователей, инкапсулируйте это в вспомогательный метод более высокого порядка:
static void WithTransaction(Action<Transaction> action)
{
Transaction t = new Transaction();
try
{
action(t);
}
catch
{
t.Rollback();
throw;
}
t.Commit();
}
....
WithTransaction(t=>DoStuff(t));
Комментарии:
1. Как насчет использования
RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup Method
? Я видел, как это упоминалось на странице с ограниченными областями выполнения .2. @Brian: Верно, в принципе, это то, что я здесь набрасываю. Обычно этот метод используется только авторами компиляторов, которые генерируют код для языков, которые поддерживают транзакции как основную концепцию языка, но я полагаю, вы могли бы использовать его сами, если бы захотели.
3. Я полагаю, что это злоупотребление блоком using. Класс Foo будет определять время выполнения блока кода и регистрировать, если он вызвал исключение. С одной стороны, это было бы злоупотреблением языковой функцией, с другой стороны, это затруднило бы неправильное использование API.
Ответ №2:
Если Dispose()
требуется знать, была ли ошибка, или ее нужно вызывать, только если все прошло нормально, то это плохая Dispose
реализация.
Dispose
речь идет не об обработке ошибок, а о том, чтобы гарантировать, что при любых условиях ваш неуправляемый ресурс будет освобожден.
Вы бы выполнили обработку ошибок как обычно, но поместили using
блок внутри try
блока:
try
{
using(...)
{
...
}
}
catch(Exception e)
{
...
}
Ответ №3:
Возможно ли определить разницу между обычным выходом блока using() и […] генерируемым исключением?
Это не цель блока using, поэтому: Нет.
catch()
{
f.ThereWasAnError();
}
Если вам действительно нужна какая-то форма обработки ошибок внутри ваших объектов, тогда встроите ее. Это не должно нуждаться в посторонней помощи. Это, вероятно, потребовало бы try / catch в каждом общедоступном методе, но такова цена.
Но я бы с большим подозрением отнесся к такому дизайну.
Ответ №4:
Я не рекомендую следующее. Как пишет Эрик Липперт, это злоупотребление одноразовым шаблоном.
При этом должно сработать следующее:
public void Dispose(){
if(Marshal.GetExceptionCode()==0){
// disposing normally
Commit();
}else{
// disposing because of exception.
Rollback();
}
}
Ответ №5:
Насколько я знаю, нет, но на первый взгляд я бы пропустил блок catch и позволил всплывающей ошибке всплыть. YMMV.
Ответ №6:
Нет никакого способа заставить CLR сообщить вашему Dispose
методу, что вот-вот будет выдано исключение.
Но странная вещь заключается в том, что вы хотите «уведомить» свой собственный Foo
класс об исключении, которое произошло в его собственном Stuff
методе. Делать это очень необычно; вы когда-нибудь видели класс, который вам нужно уведомить о том, что он только что выдал исключение?
Вы должны использовать IDisposable
шаблон для выполнения некоторой неуправляемой очистки после того, как ваш класс больше не нужен. Исходя из вашего вопроса, я подозреваю, что ваш класс выполняет множество реальных функций внутри Dispose
метода.
Ответ №7:
используйте try{}catch{} внутри блока using().
Ответ №8:
Что ж. Если при удалении вас интересует только то, было ли вызвано исключение удаляемым экземпляром, вы могли бы кэшировать исключение:
class Foo
{
Exception _thrownException;
void Stuff()
{
try
{
//...
}
catch (Exception e)
{
_thrownException = e;
throw;
}
//... I suppose you get the idea from here.
Однако это не позволит вам узнать, вызвало ли исключение что-то другое в блоке using, например:
using (var f = new Foo())
{
int i = f.IntegerMethod() / 0;
...
}
Ответ №9:
Я выполнил
using(Foo f = new Foo())
{
f.stuff();
f.IsOk();
}
Затем метод dispose знает, был ли он вызван из-за исключения или просто скрыл конец блока «try» или «using».
Иногда «isOk()» называется «Commit()»…