Подтвердить что ты не робот

"использование" конструкции и обработки исключений

" using" Конструкция выглядит невероятно удобной для ситуаций, требующих как начальных, так и разделенных конечных частей.

Быстрый пример для иллюстрации:

using (new Tag("body")) {
    Trace.WriteLine("hello!");
}
// ...
class Tag : IDisposable {
    String name;
    public Tag(String name) {
        this.name = name;
        Trace.WriteLine("<" + this.name + ">");
        Trace.Indent();
    }
    public void Dispose() {
        Trace.Unindent();
        Trace.WriteLine("</" + this.name + ">")
    }
}

Начальная часть определяется как конструктор, конечной частью является метод Dispose.

Однако, несмотря на привлекательность, эта конструкция имеет серьезное предостережение, которое исходит из того факта, что метод Dispose вызывается из блока finally. Итак, есть 2 проблемы:

  • Вам следует избегать бросать исключения из блока finally, потому что они будут переопределять исходное исключение, которое должно было быть уловлено.

  • Не существует способа узнать внутри метода Dispose, если исключение было выбрано ранее между "началом" и "завершением", и, следовательно, нет возможности обработать "конец" соответственно.

Эти две вещи делают использование этой конструкции непрактичным, что является очень грустным фактом. Теперь мои вопросы:

  • Насколько я правильно понимаю проблемы? Это как работает "использование"?

  • Если это так, есть ли способ преодолеть эти проблемы и использовать практическое использование "использования", кроме того, из чего он был первоначально предназначен (освобождение ресурсов и очистка)

  • Если нет практического способа "использования" для использования таким образом. Каковы альтернативные подходы (для обеспечения соответствия контекста некоторому коду с начальной и конечной частями)?

4b9b3361

Ответ 1

Цель оператора using и интерфейса IDisposable - это избавиться от неуправляемых ресурсов. Эти ресурсы обычно дорогие и драгоценные, поэтому их нужно утилизировать независимо от того, что (почему это на finally). Код в блоке finally даже не может быть прерван, и он может повредить полное завершение домена приложения.

Теперь очень заманчиво злоупотреблять using для целей, которые вы описываете, и я делал это в прошлом. Большую часть времени там нет опасности там. Но если произойдет непредвиденное исключение, все состояние обработки будет скомпрометировано, вы не обязательно захотите запустить конечную операцию; так что вообще не делайте этого.

Альтернативой является использование лямбда, что-то вроде этого:

public interface IScopable { 
  void EndScope();
}

public class Tag : IScopable {
  private string name;
  public Tag(string name) {
    this.name = name;
    Trace.WriteLine("<" + this.name + ">");
    Trace.Indent();
  }
  public void EndScope() {
    Trace.Unindent();
    Trace.WriteLine("</" + this.name + ">");
  }
}

public static class Scoping {
  public static void Scope<T>(this T scopable, Action<T> action) 
    where T : IScopable {
    action(scopable);
    scopable.EndScope();
  }
}

Используйте его следующим образом:

new Tag("body").Scope(_ => 
  Trace.WriteLine("hello!")
);

Вы также можете создавать другие реализации, которые запускают определенные действия, исходя из того, были ли исключены исключения.

В Nemerle язык может быть расширен с помощью нового синтаксиса для поддержки этого.

Ответ 2

Ваше правило № 1 применяется с или без using, поэтому правило №2 является реальным решателем: выберите try/catch, если вы должны различать ситуации, когда выбрано исключение, и нормальный завершение программы.

Например, если ваш уровень персистентности может обнаружить проблемы в процессе использования подключения к базе данных, ваше соединение должно быть закрыто независимо от того, что произошло исключение. В этом случае конструкция using является идеальным выбором.

В некоторых случаях вы можете настроить using специально для обнаружения нормального завершения и исключительного завершения. Экземпляры обеспечивают отличный пример:

using(TransactionScope scope = new TransactionScope()) {
    // Do something that may throw an exception
    scope.Complete();
}

Если scope Dispose вызывается до того, как был вызван Complete, TransactionScope знает, что выбрано исключение, и прерывает транзакцию.

Ответ 3

Я не знаю, было ли это первоначальное намерение IDisposable, но Microsoft certanly ARE использует его так, как вы описываете (разделяя начальную и конечную части). Хорошим примером этого является класс MVCForm, предоставляемый инфраструктурой mvc. Он реализует IDisposable и записывает конечный тег для формы, в то время как я не вижу его реализации, освобождая ресурсы ant (писатель, используемый там для вывода html, кажется, остается в живых даже после того, как форма расположена).
Ало было написано о блоке using и как оно "проглатывает" исключения (a wcf client является хорошим образцом, вы также можете найти такие обсуждения здесь, на SO). Лично я также чувствую много раз, что так удобно, как использовать блок using, его на самом деле не на 100% яснее, когда нужно, и когда он не должен использоваться.

Конечно, вы действительно МОЖЕТЕ сказать в рамках метода dispose, если вы достигли его с нашей без ошибок, добавив дополнительный флаг к вашему классу и подняв его в блоке using, но это будет работать только в том случае, если человек, который будет использовать ваш класс, будет знать этот флаг

Ответ 4

Вы правильно заметили проблему с дизайном блоков try/finally, что, в свою очередь, является проблемой с using: нет чистого пути для кода в блоке finally, чтобы узнать, продолжит ли выполнение кода с инструкцией, следующей за блоком finally, или существует ли ожидающее исключение, которое будет эффективно захвачено, как только выполняется блок finally.

Мне бы очень хотелось увидеть функцию языка в vb.net и С#, которая позволила бы блоку finally включить параметр Exception (например,

  try
  {
  }
  finally (Exception ex)
  {
    ...
  }

где исключаемое исключение было бы null, если бы блок try выходил нормально или имел бы исключение, если бы оно не было. Наряду с этим я хотел бы увидеть интерфейс IDisposableEx, который наследует Dispose и включает метод Dispose(Exception ex) с ожиданием, что код пользователя будет передан в ex из блока finally. Любые исключения, которые произошли во время Dispose, могли бы затем обернуть исключение с переходом (поскольку было бы релевантно как исключение в качестве исключения, так и тот факт, что исключение произошло в Dispose).

В противном случае было бы полезно, чтобы .net предоставлял метод, который указывал бы, было ли исключение ожидающим в текущем контексте. К сожалению, неясно, какая точная семантика такого метода должна быть в разных угловых случаях. Напротив, семантика finally (Exception ex) была бы совершенно ясна. Обратите внимание, что, кстати, правильная реализация finally (Exception ex) потребует, чтобы язык использовал фильтры исключений, но не требовал раскрытия возможности создавать произвольные фильтры.

Ответ 5

По-моему, вы злоупотребляете интерфейсом IDisposable. Общей практикой является использование интерфейса для освобождения неуправляемых ресурсов. Обычно сборщик мусора очищает объекты, но в некоторых случаях - когда вам больше не нужен объект - вам может понадобиться вручную очистить.

Однако в вашем случае вы не очищаете объект, который больше не нужен; вы используете его, чтобы заставить какую-то логику. Вы должны использовать для этого другой дизайн.