Почему «используется» улучшение производительности C # - PullRequest
13 голосов
/ 17 июня 2010

Похоже, что в большинстве случаев компилятор C # может автоматически вызывать Dispose(). Как и большинство случаев с использованием шаблона , выглядит так:

public void SomeMethod()
{
    ...

    using (var foo = new Foo())
    {
        ...
    }

    // Foo isn't use after here (obviously).
    ...
}

Поскольку foo не используется (это очень простое обнаружение) и поскольку оно не предоставляется в качестве аргумента для другого метода (это предположение, которое применяется ко многим случаям использования и может быть расширено), компилятор может автоматически и немедленно звоните Dispose() без необходимости делать это разработчику.

Это означает, что в большинстве случаев using довольно бесполезен, если компилятор выполняет какую-то умную работу. IDisposable кажется низким уровнем мне достаточно, чтобы компилятор учел это.

Теперь, почему это не сделано? Разве это не улучшит производительность (если разработчики ... грязные ).

Ответы [ 11 ]

21 голосов
/ 17 июня 2010

Пара моментов:

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

Не существует четкого и очевидного механизма того, как компилятор может обрабатывать IDisposable объектыв коде.Что делает его кандидатом на автоматическое удаление, а что нет?Если экземпляр (или мог) быть представлен вне метода?Нечего и говорить, что только потому, что я передаю объект другой функции или классу, который я хочу, чтобы он был пригоден для использования вне рамок метода

Рассмотрим, например, фабричный шаблон, который принимает Stream идесериализует экземпляр класса.

public class Foo
{
    public static Foo FromStream(System.IO.Stream stream) { ... }
}

И я называю это:

Stream stream = new FileStream(path);

Foo foo = Foo.FromStream(stream);

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

Аналогично, как насчет экземпляров, которые извлекаются из чего-то другого, кроме конструктора, например Control.CreateGraphics().Эти экземпляры могут существовать вне кода, поэтому компилятор не будет их утилизировать автоматически.

Предоставление пользовательского элемента управления (и обеспечение идиомы, подобного блоку using) проясняет намерение пользователя и значительно упрощает определение мест, где экземпляры IDisposable не утилизируются должным образом.Если бы компилятор автоматически избавлялся от некоторых экземпляров, то отладка была бы намного сложнее, так как разработчику пришлось бы расшифровывать, как правила автоматического удаления применяются к каждому блоку кода, который использовал IDisposableobject.

Наконец, есть две причины (по соглашению) для реализации IDisposable для типа.

  1. Вы используете неуправляемый ресурс (то есть вы делаетевызов P / Invoke, который возвращает что-то вроде дескриптора, который должен быть освобожден другим вызовом P / Invoke)
  2. В вашем типе есть экземпляры IDisposable, которые следует утилизировать, когда закончится время жизни этого объекта.

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

16 голосов
/ 17 июня 2010

Сборка мусора (хотя она не связана напрямую с IDisposable, это то, что очищает неиспользуемые объекты) не так просто.

Позвольте мне перефразировать это немного. Автоматический вызов Dispose() не так прост. Это также не увеличит производительность напрямую. Подробнее об этом чуть позже.

Если у вас был следующий код:

public void DoSomeWork(SqlCommand command)
{
    SqlConnection conn = new SqlConnection(connString);

    conn.Open();

    command.Connection = conn;

    // Rest of the work here
 }

Как компилятор узнает, когда вы закончили использовать объект conn? Или если вы передали ссылку на какой-то другой метод, который его держал?

Явный вызов Dispose() или использование блока using четко определяет ваше намерение и заставляет вещи правильно очищаться.

Теперь вернемся к производительности. Простой вызов Dispose() для объекта не гарантирует увеличения производительности. Dispose() метод используется для «очистки» ресурсов, когда вы закончите с Объектом.

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

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

3 голосов
/ 17 июня 2010

Посмотрите на MSDN Artilce для оператора C # Using Оператор using - это всего лишь краткий путь, чтобы не делать попыток и, наконец, во всех местах.Вызов dispose не является функциональностью низкого уровня, как сборщик мусора.

Как видите, использование переведено в.

{
  Font font1 = new Font("Arial", 10.0f);
  try
  {
    byte charset = font1.GdiCharSet;
  }
  finally
  {
    if (font1 != null)
      ((IDisposable)font1).Dispose();
  }
}

Как компилятор узнает, куда поместить блок finally?Вызывает ли это вызов в сборке мусора?

Сбор мусора не происходит, как только вы покидаете метод.Прочтите эту статью о сборке мусора, чтобы лучше понять ее.Только после того, как нет ссылок на объект.Ресурс может быть связан дольше, чем нужно.

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

3 голосов
/ 17 июня 2010

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

Также могут быть проблемы с изменчивыми объектами.

Кроме того, using() {....} более читабелен и интуитивно понятен, что очень важно с точки зрения удобства сопровождения.

Как инженеры или программисты, мы стремимся быть эффективными , но это редко то же самое, что lazy .

2 голосов
/ 17 июня 2010

Я думаю, что вы думаете о финализаторах. Финализаторы используют синтаксис деструктора в c #, и они автоматически вызываются сборщиком мусора. Финализаторы подходят только для очистки неуправляемых ресурсов.

Утилита предназначена для ранней очистки неуправляемых ресурсов (а также может использоваться для очистки управляемых ресурсов).

Обнаружение на самом деле сложнее, чем кажется. Что делать, если у вас есть такой код:


  var mydisposable = new...
  AMethod(mydisposable);
  // (not used again)

Возможно, что некоторый код в AMethod содержит ссылку на myDisposable.

  • Может быть, он назначен переменной экземпляра внутри этого метода

  • Возможно, myDisposable подписывается на событие внутри AMethod (тогда издатель событий содержит ссылку на myDisposable)

  • Может быть, AMethod создает другую нить

  • Может быть, mydisposable становится "закрытым" анонимным методом или выражением lamba внутри AMethod.

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

Помните также, что утилизация не освобождает ваш объект - это может сделать только GC. (У GC есть волшебство, чтобы понять все сценарии, которые я описал, и он знает, когда нужно очистить ваш объект, и если вам действительно нужен код для запуска, когда GC не обнаруживает ссылок, вы можете использовать финализатор). Однако будьте осторожны с финализаторами - они предназначены только для неуправляемых распределений, которыми владеет ваш класс.

Подробнее об этом можно прочитать здесь: http://msdn.microsoft.com/en-us/magazine/bb985010.aspx и здесь: http://www.bluebytesoftware.com/blog/2005/04/08/DGUpdateDisposeFinalizationAndResourceManagement.aspx

Если вам нужна неуправляемая очистка дескриптора, прочтите также о SafeHandles.

2 голосов
/ 17 июня 2010

C ++ поддерживает это; они называют это "семантикой стека для ссылочных типов". Я поддерживаю добавление этого в C #, но это потребует другого синтаксиса (изменение семантики в зависимости от того, передана ли локальная переменная другому методу, не является хорошей идеей).

1 голос
/ 17 июня 2010

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

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

0 голосов
/ 18 июня 2010

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

0 голосов
/ 18 июня 2010

Я думаю, что ОП говорит: «зачем беспокоиться о« использовании », когда компилятор должен иметь возможность легко и магически решить его».

Я думаю, что ОП говорит, что

public void SomeMethod() 
{ 
    ... 

    var foo = new Foo();

    ... do stuff with Foo ... 


    // Foo isn't use after here (obviously). 
    ... 
} 

должно быть эквивалентно

public void SomeMethod() 
{ 
    ... 

    using (var foo = new Foo())
    {
    ... do stuff with Foo ... 
    }

    // Foo isn't use after here (obviously). 
    ... 
} 

, потому что Foo больше не используется.

Ответ, конечно, заключается в том, что компилятор не может решить это довольно легко.Сборка мусора (что волшебным образом называется «Dispose ()» в .NET) - очень сложная область.Тот факт, что символ не используется ниже, не означает, что переменная не используется.

Возьмем этот пример:

public void SomeMethod() 
{ 
    ... 

    var foo = new Foo();
    foo.DoStuffWith(someRandomObject);
    someOtherClass.Method(foo);

    // Foo isn't use after here (obviously).
    // Or is it?? 
    ... 
} 

В этом примере someRandomObject и someOtherClass может иметь ссылки на то, на что указывает Foo, поэтому, если бы мы вызвали Foo.Dispose (), это сломало бы их.Вы говорите, что представляете себе простой случай, но единственный «простой случай», в котором работает то, что вы предлагаете, - это случай, когда вы не делаете никаких вызовов методов из Foo и не передаете Foo или любой из его членов чему-либо еще -фактически, когда вы вообще не используете Foo, в этом случае вам, вероятно, нет нужды объявлять его.Даже в этом случае, вы никогда не можете быть уверены, что какое-то отражение или хакерское событие не получили ссылку на Foo просто его созданием, или что Foo не связывал себя с чем-то другим во время своего конструктора.

0 голосов
/ 18 июня 2010

Оператор using не имеет ничего общего с производительностью (если вы не рассматриваете возможность предотвращения утечек ресурсов / памяти в качестве производительности).

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

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

Конечно, если метод Dispose () реализован плохо, оператор using дает нулевую выгоду.

...