Случаи, когда оператор использования и IDisposable никогда не должны использоваться

Я читал об этом сценарии, где использование оператора использования C# может вызвать проблемы. Исключения, выданные в рамках блока использования, могут быть потеряны, если Расположить функция, вызванная в конце оператора использования, должна была выдать исключение также. Это выделяется, ту заботу нужно соблюдать в определенных случаях при решении, добавить ли оператор использования.

Я только склонен использовать использование операторов при использовании потоков и классов, полученных из DbConnection. Если бы я должен очистить неуправляемые ресурсы, я обычно предпочитал бы использовать наконец блок.

Это - другое использование интерфейса IDisposable для создания таймера производительности, который остановит таймер и зарегистрирует время к реестру в Расположить функции. http://thebuildingcoder.typepad.com/blog/2010/03/performance-profiling.html

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

Есть ли времена, когда оператор использования и интерфейс IDisposable никогда не должны использоваться? Имеет реализация IDisposable, или обертывание кода в операторе использования вызвало проблемы для Вас прежде?

Спасибо

8
задан fletcher 20 July 2010 в 17:40
поделиться

5 ответов

Я бы сказал, всегда используйте using, если только документация не говорит вам этого (как в вашем примере).

Наличие метода Dispose, выбрасывающего исключения, скорее уничтожает смысл его использования (каламбур). Когда бы я его ни реализовывал, я всегда стараюсь гарантировать, что никакие исключения не будут выбрасываться из него независимо от того, в каком состоянии находится объект.

PS: Вот простой метод утилиты для компенсации поведения WCF. Это гарантирует, что Abort вызывается на каждом пути выполнения, кроме того, когда вызывается Close, и что ошибки передаются вызывающей стороне.

public static void CallSafely<T>(ChannelFactory<T> factory, Action<T> action) where T : class {
    var client = (IClientChannel) factory.CreateChannel();
    bool success = false;
    try {
        action((T) client);
        client.Close();
        success = true;
    } finally {
        if(!success) {
            client.Abort();
        }
    }
}

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

5
ответ дан 5 December 2019 в 12:54
поделиться

Я думаю, что более серьезная проблема - это выброс исключений в Dispose . Шаблоны RAII обычно прямо заявляют, что этого делать не следует, поскольку это может создать ситуации, подобные этой. Я имею в виду, каков путь восстановления для чего-то, что не удаляется правильно, кроме простого завершения выполнения?

Кроме того, похоже, что этого можно избежать с помощью двух операторов try-catch:

try
{
    using(...)
    {
        try
        {
            // Do stuff
        }
        catch(NonDisposeException e)
        {
        }
    }
}
catch(DisposeException e)
{
}

Единственная проблема, которая может здесь возникнуть, это если DisposeException совпадает с NonDisposeException или является его супертипом, и вы пытаетесь повторно выбросить из ловушки NonDisposeException . В этом случае блок DisposeException перехватит его. Таким образом, вам может потребоваться дополнительный логический маркер, чтобы проверить это.

2
ответ дан 5 December 2019 в 12:54
поделиться

Единственный известный мне случай - это клиенты WCF. Это связано с ошибкой конструкции в WCF - Dispose не должен никогда генерировать исключения. Они пропустили это.

2
ответ дан 5 December 2019 в 12:54
поделиться

Одним из примеров является свойство IAsyncResult.AsyncWaitHandle . Проницательный программист поймет, что классы WaitHandle реализуют IDisposable и, естественно, попытаются с жадностью избавиться от них. За исключением того, что большинство реализаций APM в BCL фактически выполняют отложенную инициализацию WaitHandle внутри свойства. Очевидно, в результате программист проделал больше работы, чем было необходимо.

Так где же поломка? Что ж, Microsoft испортила интерфейс IAsyncResult . Если бы они следовали своему собственному совету, IAsyncResult был бы производным от IDisposable , поскольку подразумевается, что он содержит одноразовые ресурсы. Проницательный программист тогда просто вызовет Dispose для IAsyncResult и позволит ему решить, как лучше всего расположить его составляющие.

Это один из классических второстепенных случаев, когда избавление от IDisposable может быть проблематичным. Джеффри Рихтер фактически использует этот пример, чтобы аргументировать (ошибочно, на мой взгляд), что вызов Dispose не является обязательным. Вы можете прочитать дебаты здесь .

1
ответ дан 5 December 2019 в 12:54
поделиться

Общее практическое правило простое: когда класс реализует IDisposable, используйте , используя . Когда вам нужно отловить ошибки, используйте try / catch / finally , чтобы иметь возможность отловить ошибки.

Однако есть несколько замечаний.

  1. Вы спрашиваете, существуют ли ситуации, когда IDisposable не следует использовать. Что ж: в большинстве случаев вам не нужно его реализовывать. Используйте его, когда вы хотите своевременно освободить ресурсы, а не ждать, пока сработает финализатор.

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

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

  4. Использование с использованием - это одно, но не путайте его с использованием , попробуйте / finally . Оба они равны, но оператор using упрощает жизнь, добавляя проверку области видимости и проверку на null, что каждый раз сложно делать вручную.Оператор using переводится в это (из стандарта C #):

     {
    SomeType withDispose = новый SomeType ();
    пытаться
     {
     // использовать withDispose
     }
    наконец-то
     {
    если (withDispose! = ноль)
     {
     ((IDisposable) withDispose) .Dispose ();
     }
     }
    }
    
  5. Бывают случаи, когда обертывание объекта в использующий блок не требуется. Такие случаи редки. Они случаются, когда вы обнаруживаете, что наследуете интерфейс, унаследованный от IDisposable , на всякий случай дочерний элемент потребует удаления. Часто используемый пример - IComponent, который используется с каждым элементом управления (Form, EditBox, UserControl, вы называете его). И я редко вижу, чтобы люди заключали все эти элементы управления в using-выражения. Другой известный пример - IEnumerator . При использовании его потомков также редко можно встретить блоки using.

Заключение

Используйте оператор using повсеместно и рассудительно относитесь к альтернативам или опускайте их. Убедитесь, что вы знаете о последствиях (не) его использования, а также о равенстве с использованием и try / finally. Нужно что-нибудь поймать? Используйте try / catch / finally.

3
ответ дан 5 December 2019 в 12:54
поделиться
Другие вопросы по тегам:

Похожие вопросы: