Как расположить класс в .net? - PullRequest
42 голосов
/ 15 августа 2008

Сборщик мусора .NET со временем освободит память, но что, если вы хотите немедленно вернуть эту память? Какой код нужно использовать в классе MyClass для вызова

MyClass.Dispose()

и освободите все использованное пространство переменными и объектами в MyClass?

Ответы [ 20 ]

100 голосов
/ 15 августа 2008

IDisposable не имеет ничего общего с освобождением памяти. IDisposable - это шаблон для освобождения неуправляемых ресурсов, а память - это определенно управляемый ресурс.

Ссылки, указывающие на GC.Collect (), являются правильным ответом, хотя использование этой функции обычно не рекомендуется в документации Microsoft .NET.

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

Внутри .NET-процесса существует два вида ресурсов - управляемый и неуправляемый. «Управляемый» означает, что среда выполнения контролирует ресурс, а «неуправляемый» означает, что это является обязанностью программиста. И на самом деле в .NET сегодня есть только один вид управляемых ресурсов - память. Программист говорит среде выполнения выделять память, и после этого ей нужно выяснить, когда память может быть освобождена. Механизм, который .NET использует для этой цели, называется сборщик мусора , и вы можете найти много информации о GC в Интернете просто с помощью Google.

Что касается других видов ресурсов, .NET ничего не знает об их очистке, поэтому он должен полагаться на программиста, чтобы поступать правильно. Для этого платформа предоставляет программисту три инструмента:

  1. Интерфейс IDisposable и оператор "using" в VB и C #
  2. Финализаторы
  3. Шаблон IDisposable, реализованный многими классами BCL

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

using (DisposableObject tmp = DisposableObject.AcquireResource()) {
    // Do something with tmp
}
// At this point, tmp.Dispose() will automatically have been called
// BUT, tmp may still a perfectly valid object that still takes up memory

Если «AcquireResource» является фабричным методом, который (например) открывает файл, а «Dispose» автоматически закрывает файл, то этот код не может утечь файловый ресурс. Но память для самого объекта "tmp" вполне может быть выделена. Это связано с тем, что интерфейс IDisposable абсолютно не связан с сборщиком мусора. Если вы действительно хотели убедиться, что память была освобождена, единственным вариантом будет вызвать GC.Collect() для принудительного сбора мусора.

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

Что произойдет, если ресурс используется в течение более длительного периода времени, так что его срок службы пересекается с несколькими методами? Понятно, что выражение «using» больше не применимо, поэтому программисту придется вручную вызывать «Dispose», когда он или она закончили с ресурсом. А что будет, если программист забудет? Если нет запасного варианта, процесс или компьютер могут в конечном итоге исчерпать ресурс, который не был должным образом освобожден.

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

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

Для большинства ресурсов нам нужны обе эти вещи. Мы хотим, чтобы соглашение могло сказать «мы закончили с этим ресурсом сейчас», и мы хотим убедиться, что есть хотя бы некоторый шанс, что очистка произойдет автоматически, если мы забудем сделать это вручную. Вот где в игру вступает паттерн «IDisposable». Это соглашение, которое позволяет IDispose и финализатору хорошо играть вместе. Вы можете увидеть, как работает шаблон, обратившись к официальной документации для IDisposable .

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

22 голосов
/ 15 августа 2008

Вы можете распоряжаться только теми экземплярами, которые реализуют интерфейс IDisposable.

Чтобы заставить сборщик мусора немедленно освободить (неуправляемую) память:

GC.Collect();  
GC.WaitForPendingFinalizers();

Обычно это плохая практика, но есть, например, ошибка в x64-версии .NET Framework, из-за которой GC ведет себя странно в некоторых сценариях, и тогда вы можете захотеть это сделать. Я не знаю, была ли ошибка устранена. Кто-нибудь знает?

Чтобы избавиться от класса, вы делаете это:

instance.Dispose();

или как это:

using(MyClass instance = new MyClass())
{
    // Your cool code.
}

, который будет переведен во время компиляции в:

MyClass instance = null;    

try
{
    instance = new MyClass();        
    // Your cool code.
}
finally
{
    if(instance != null)
        instance.Dispose();
}

Вы можете реализовать интерфейс IDisposable следующим образом:

public class MyClass : IDisposable
{
    private bool disposed;

    /// <summary>
    /// Construction
    /// </summary>
    public MyClass()
    {
    }

    /// <summary>
    /// Destructor
    /// </summary>
    ~MyClass()
    {
        this.Dispose(false);
    }

    /// <summary>
    /// The dispose method that implements IDisposable.
    /// </summary>
    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// The virtual dispose method that allows
    /// classes inherithed from this one to dispose their resources.
    /// </summary>
    /// <param name="disposing"></param>
    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources here.
            }

            // Dispose unmanaged resources here.
        }

        disposed = true;
    }
}
19 голосов
/ 31 августа 2008

Ответы на этот вопрос запутались более чем немного.

Название спрашивает об утилизации, но затем говорит, что они хотят немедленно вернуть память.

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

.Net решает, когда лучше очистить и освободить память, а не вы, как кодер .Net.

Dispose - это способ сообщить .Net, что вы покончили с чем-то, но на самом деле он не освободит память, пока не наступит наилучшее время для этого.

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

В .Net вы можете использовать GC.Collect(), чтобы заставить его немедленно, но это почти всегда плохая практика. Если .Net еще не очистил его, это означает, что сейчас не самое подходящее время для этого.

GC.Collect() подбирает объекты, которые .Net идентифицирует как выполненные. Если вы не удалили объект, который нуждается в этом, .Net может решить сохранить этот объект. Это означает, что GC.Collect() эффективен, только если вы правильно внедрили свои одноразовые экземпляры.

GC.Collect() является не заменой для правильного использования IDisposable.

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


99% времени в .Net следующие рекомендации:

Правило 1: Если вы не имеете дела с неуправляемым или с тем, что реализует IDisposable, тогда не беспокойтесь об утилизации.

Правило 2: Если у вас есть локальная переменная, реализующая IDisposable, убедитесь, что вы избавляетесь от нее в текущей области видимости:

//using is best practice
using( SqlConnection con = new SqlConnection("my con str" ) )
{
    //do stuff
} 

//this is what 'using' actually compiles to:
SqlConnection con = new SqlConnection("my con str" ) ;
try
{
    //do stuff
}
finally
{
    con.Dispose();
}

Правило 3: Если у класса есть свойство или переменная-член, которая реализует IDisposable, то этот класс должен также реализовать IDisposable. В методе Dispose этого класса вы также можете избавиться от своих свойств IDisposable:

//rather basic example
public sealed MyClass :
   IDisposable
{   
    //this connection is disposable
    public SqlConnection MyConnection { get; set; }

    //make sure this gets rid of it too
    public Dispose() 
    {
        //if we still have a connection dispose it
        if( MyConnection != null )
            MyConnection.Dispose();

        //note that the connection might have already been disposed
        //always write disposals so that they can be called again
    }
}

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

Правило 4: Если класс использует неуправляемый ресурс, то реализуйте IDispose и добавьте финализатор.

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

Метод Dispose должен работать как с управляемыми , так и неуправляемыми ресурсами.

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

~MyClass()
{
    //calls a protected method 
    //the false tells this method
    //not to bother with managed
    //resources
    this.Dispose(false);
}

public void Dispose()
{
    //calls the same method
    //passed true to tell it to
    //clean up managed and unmanaged 
    this.Dispose(true);

    //as dispose has been correctly
    //called we don't need the 

    //'backup' finaliser
    GC.SuppressFinalize(this);
}

Наконец, перегрузка Dispose, принимающая логический флаг:

protected virtual void Dispose(bool disposing)
{
    //check this hasn't been called already
    //remember that Dispose can be called again
    if (!disposed)
    {
        //this is passed true in the regular Dispose
        if (disposing)
        {
            // Dispose managed resources here.
        }

        //both regular Dispose and the finaliser
        //will hit this code
        // Dispose unmanaged resources here.
    }

    disposed = true;
}

Обратите внимание, что как только все это будет создано, другой управляемый код, создающий экземпляр вашего класса, может просто обрабатывать его как любой другой IDisposable (правила 2 и 3).

14 голосов
/ 15 августа 2008

Было бы целесообразно также упомянуть, что распоряжение не всегда относится к памяти? Я распоряжаюсь ресурсами такими ссылками на файлы чаще, чем памятью. GC.Collect () напрямую относится к сборщику мусора CLR и может освобождать или не освобождать память (в диспетчере задач). Скорее всего, это негативно скажется на вашем приложении (например, на производительности).

В конце концов, почему вы хотите немедленно вернуть память? Если в других местах возникает нехватка памяти, то в большинстве случаев ОС получит вам память.

6 голосов
/ 17 августа 2008

Взгляните на эту статью

Реализация шаблона Dispose, IDisposable и / или финализатора не имеет абсолютно никакого отношения к тому, когда память восстанавливается; вместо этого он имеет все отношение к тому, чтобы сказать GC , как вернуть эту память. Когда вы вызываете Dispose (), вы никоим образом не взаимодействуете с GC.

ГХ будет работать только тогда, когда он определит необходимость (называемая нагрузкой на память), а затем (и только тогда) освободит память для неиспользуемых объектов и сократит пространство памяти.

Вы могли бы вызвать GC.Collect (), но вы действительно не должны этого делать, если для этого нет очень веской причины (которая почти всегда "Никогда"). Когда вы запускаете подобный цикл внеполосного сбора данных, вы фактически заставляете GC выполнять больше работы и в конечном итоге можете нанести ущерб производительности ваших приложений. Во время цикла сбора GC ваше приложение фактически находится в замороженном состоянии ... чем больше циклов GC выполняется, тем больше времени ваше приложение проводит в замороженном состоянии.

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

Вся посылка времени выполнения для сбора мусора заключается в том, что вам не нужно беспокоиться (столько) о том, когда среда выполнения выделяет / освобождает фактическую память; вам нужно только позаботиться о том, чтобы ваш объект знал, как убирать за собой после запроса.

3 голосов
/ 15 августа 2008
public class MyClass : IDisposable
{
    public void Dispose()
    {
       // cleanup here
    }
}

тогда вы можете сделать что-то вроде этого

MyClass todispose = new MyClass();
todispose.Dispose(); // instance is disposed right here

или

using (MyClass instance = new MyClass())
{

}
// instance will be disposed right here as it goes out of scope
3 голосов
/ 26 августа 2008

Полное объяснение Джо Даффи о « Распоряжении, финализации и управлении ресурсами »:

Ранее в .NET Framework время жизни, финализаторы были последовательно упоминается как деструкторы в C # программисты. Как мы становимся умнее время, мы пытаемся прийти к соглашению с тем, что метод Dispose действительно более эквивалентен C ++ деструктор (детерминированный) , а финализатор это что-то полностью отдельный (недетерминированный) . Факт что C # позаимствовал деструктор C ++ синтаксис (т. е. ~ T ()) наверняка имел по крайней мере немного связано с развитием этот неправильный.

3 голосов
/ 03 мая 2012

Я написал сводку «Уничтожители и утилизация и сборка мусора» на http://codingcraftsman.wordpress.com/2012/04/25/to-dispose-or-not-to-dispose/

Чтобы ответить на оригинальный вопрос:

  1. Не пытайтесь управлять своей памятью
  2. Утилизация - это не управление памятью, а неуправляемое управление ресурсами
  3. Финализаторы являются врожденной частью шаблона Dispose и фактически замедляют освобождение памяти управляемых объектов (так как они должны идти в очередь Finalization, если только не Dispose d)
  4. GC.Collect - это плохо, так как некоторые недолговечные объекты кажутся необходимыми дольше, что замедляет их сбор.

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

Кроме того, есть аргумент в пользу этого паттерна:

var myBigObject = new MyBigObject(1);
// something happens
myBigObject = new MyBigObject(2);
// at the above line, there are temporarily two big objects in memory and neither can be collected

против

myBigObject = null; // so it could now be collected
myBigObject = new MyBigObject(2);

Но главный ответ заключается в том, что сборщик мусора просто работает, если только вы не возитесь с ним!

2 голосов
/ 15 августа 2008

Вы не можете заставить GC очистить объект, когда захотите, хотя есть способы заставить его работать, ничто не говорит, что он очищает весь объект, который вы хотите / ожидаете. Лучше всего вызывать dispose в try catch ex, наконец, dispose end try (VB.NET rulz). Но Dispose предназначен для очистки системных ресурсов (памяти, дескрипторов, соединений БД и т. Д., Выделенных объектом детерминированным способом. Dispose не очищает (и не может) очищать память, используемую самим объектом, только GC. может сделать это.

1 голос
/ 15 августа 2008

Эта статья имеет довольно простое прохождение. Однако наличие для вызова GC вместо того, чтобы позволить ему идти своим чередом, обычно является признаком плохого проектирования / управления памятью, особенно , если не используются ограниченные ресурсы (соединения, дескрипторы все остальное, что обычно приводит к реализации IDisposable).

Что заставляет вас делать это?

...