Почему `` использует '' улучшение производительности C #

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

public void SomeMethod()
{
    ...

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

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

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

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

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


person Wernight    schedule 17.06.2010    source источник
comment
это не так просто.   -  person Amy B    schedule 17.06.2010
comment
Сначала я просто рассматривал простой случай, который на самом деле довольно распространен.   -  person Wernight    schedule 17.06.2010
comment
Вы не можете проектировать только для простого случая. Очень мало случаев, когда ситуация настолько ясна.   -  person Adam Robinson    schedule 17.06.2010
comment
Если он все еще работает, как это улучшить производительность? Также он изменяет порядок выполнения действий (в MVC BeginForm может быть заблокирован).   -  person Rangoric    schedule 17.06.2010


Ответы (11)


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

Вызов 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 экземпляры не удаляются должным образом. Если бы компилятор автоматически удалял некоторые экземпляры, то отладка была бы намного сложнее, поскольку разработчику приходилось расшифровывать, как правила автоматического удаления применяются к каждому блоку кода, который использовал объект IDisposable. .

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

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

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

person Adam Robinson    schedule 17.06.2010
comment
Я думаю, что ответ более ясен, чем текущий результат, поскольку он объясняет, для чего используется IDisposable. - person Tamás Szelei; 17.06.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() компилятору снизит этот уровень ясности и значительно затруднит отладку утечек памяти, вызванных неуправляемыми ресурсами.

person Justin Niessner    schedule 17.06.2010
comment
Ваш пример на самом деле ничего не объясняет - без контекста нахождения в методе ясно, что компилятор сразу же после завершения метода знает, что объект conn больше не нужен. - person David_001; 17.06.2010
comment
@ David_001 - Я как раз думал об этом. Я вернусь к моему предыдущему примеру. - person Justin Niessner; 17.06.2010
comment
Сборка мусора не так проста. - Для справки, IDisposable не имеет никакого отношения к сборщику мусора. Распространено заблуждение, что вызов Dispose() каким-то образом вызывает сборщик мусора, но Dispose() - это просто еще один метод, ничего особенного в нем нет. - person Matt Greer; 17.06.2010
comment
@Matt Greer - Я понимаю, что IDisposable не имеет ничего общего со сборкой мусора. Процесс, при котором Framework автоматически очищает неиспользуемые объекты (о чем он говорит, осознает он это или нет) - это сборка мусора. - person Justin Niessner; 17.06.2010
comment
Причина, по которой существует метод Dispose, заключается в надежном избавлении от некоторых неуправляемых ресурсов. Сборщик мусора не является детерминированным - он может работать в любое время или вообще не запускаться. Если неуправляемые ресурсы останутся до тех пор, пока они не будут собраны, они могут протечь и вызвать проблемы. Вот почему существует Dispose, поэтому вам нужно иметь возможность явно вызывать его. using помогает в этом. - person thecoop; 17.06.2010
comment
@Justin: достаточно честно. Казалось, что OP больше интересовался компилятором, вводящим вызовы Dispose (), а не сборщиком мусора, выясняющим это во время выполнения. Конечно, GC просто не может понять это (и, конечно же, компилятор) - person Matt Greer; 17.06.2010
comment
@ Мэтт Грир - Понятно. Моя формулировка была в лучшем случае неясной. Пытался немного почистить. Также добавлено немного больше о производительности вопроса. - person Justin Niessner; 17.06.2010
comment
@Justin Niessner - Возможно, стоит пояснить, что .Dispose() предназначен для очистки внешних ресурсов и не «очищает» объект .net (как это можно увидеть, например, с удаленными формами) . - person Codesleuth; 17.06.2010

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

Неустойчивые сущности также могут вызывать беспокойство.

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

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

person 3Dave    schedule 17.06.2010

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

Как видите, using переведено на.

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

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

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

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

person David Basarab    schedule 17.06.2010

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

person Stephen Cleary    schedule 17.06.2010
comment
IMHO, C # и VB получили бы большую выгоду, если бы у них были средства пометки поля, чтобы указать, что его следует считать одноразовым, и сгенерировать реализацию IDisposable, чтобы позаботиться о них (добавьте код перед пользовательским кодом в IDisposable, если только атрибут указывает, что он должен быть выделен в отдельный метод). Поддержка компилятора также упростит отключение событий VB.net и очистку IDisposables, созданных в инициализаторах полей (сложно для vb.net и невозможно в C #). - person supercat; 08.12.2010

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

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

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


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

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

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

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

  • Может быть, AMethod порождает еще одну ветку.

  • Возможно, mydisposable становится «замкнутым» анонимным методом или выражением lamba внутри AMethod.

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

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

Вы можете узнать больше об этом здесь: http://msdn.microsoft.com/en-us/magazine/bb985010.aspx и здесь: http://www.bluebytesoftware.com/blog/2005/04/08/DGUpdateDisposeFinalizationAndResourceManagement.aspx

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

person JMarsch    schedule 17.06.2010
comment
Я удалил свой предыдущий комментарий, потому что неправильно понял то, что вы сказали. - person Adam Robinson; 18.06.2010

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

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

person nlawalker    schedule 17.06.2010

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

person Shimrod    schedule 17.06.2010

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

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

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

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

person Ash    schedule 18.06.2010
comment
Да, я считаю это. Производительность памяти, а иногда и блокировка (когда файлы или БД открыты). - person Wernight; 18.06.2010

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

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

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 не подключился к чему-то еще во время своего конструктора.

person Matt Mitchell    schedule 18.06.2010
comment
GC не вызывает Dispose. GC не вызывает Dispose. GC не вызывает Dispose. Теперь все со мной, GC не вызывает Dispose. Сборщик мусора вызывает только финализаторы; если вам нужно вызвать Dispose, если ваш объект собран без надлежащего удаления (что только верно, если вы сами используете неуправляемые ресурсы, нет, если вы ' re просто потребляет другие IDisposable объекты), тогда вам нужно сделать вызов финализатора Dispose(false) самостоятельно, что также означает, что вам нужен Dispose(bool disposing), где Dispose вызывает Dispose(true) - person Adam Robinson; 18.06.2010
comment
Ты узнаешь что-то каждый день. Клянусь, сертификация MS заставила меня ответить, что GC вызывает Dipose. Я предполагаю, вы имеете в виду, что если я использую другие объекты IDisposable для ресурсов, они должны обрабатывать ресурс, закрывая себя, в редких случаях, когда кто-то не утилизирует правильно? - person Matt Mitchell; 18.06.2010
comment
C # не вызывает Dispose, см. stackoverflow.com/questions/1691846/ - person Wernight; 28.06.2010
comment
Да, как уже сказал @Adam Robinson. С тех пор я исправил свои знания об утилизации. Кажется, что каждое использование dispose должно включать финализатор, что-то, что документация MS рекомендует против, потому что в конечном итоге, если приложение выйдет из строя и т. Д., Вы не хотите, чтобы ваши дескрипторы ресурсов оставались open, поэтому вы в первую очередь закодировали свое распоряжение. - person Matt Mitchell; 28.06.2010
comment
@Graphain: Нет, не во всех реализациях. Есть две (традиционные) причины для реализации IDisposable: либо вы используете неуправляемые ресурсы напрямую (то есть вызываете P / Invoke и получаете назад какой-то дескриптор, который должен быть выпущен явно через другой P / Invoke call) или вы потребляете другие IDisposable объекты. Только (и всегда), когда вы используете ресурсы напрямую, вы должны использовать финализатор. Фактически, избавление от управляемых ресурсов (или даже доступ к ним вообще) в финализаторе специально запрещено. - person Adam Robinson; 28.06.2010

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

person Travis    schedule 18.06.2010