Случаи, когда оператор using и IDisposable никогда не должны использоваться - PullRequest
8 голосов
/ 20 июля 2010

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

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

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

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

Бывают ли случаи, когда оператор using и интерфейс IDisposable никогда не должны использоваться?Реализована ли ранее у вас реализация IDisposable или упаковка кода в операторе использования?

Спасибо

Ответы [ 5 ]

5 голосов
/ 20 июля 2010

Я бы сказал, всегда используйте 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();
        }
    }
}

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

3 голосов
/ 20 июля 2010

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

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

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

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

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

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

    {
        SomeType withDispose = new SomeType();
        try
        {
             // use withDispose
        }            
        finally 
        {
            if (withDispose != null)
            {
                 ((IDisposable)withDispose).Dispose();
            }
        }
    }
    
  5. В некоторых случаях упаковка объекта в блок использования не обязательна.Эти случаи редки.Они происходят, когда вы обнаруживаете, что наследуете от интерфейса, который наследует от IDisposable , на случай, если ребенок потребует избавления.Часто используемым примером является IComponent, который используется с каждым элементом управления (Form, EditBox, UserControl, вы называете его).И я редко вижу, чтобы люди включали все эти элементы управления в операторы использования.Другой известный пример - IEnumerator<T>.При использовании его потомков редко можно увидеть блоки использования.

Заключение

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

2 голосов
/ 20 июля 2010

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

2 голосов
/ 20 июля 2010

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

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

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

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

1 голос
/ 20 июля 2010

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

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

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

...