C # Деструктор не работает должным образом - PullRequest
5 голосов
/ 17 августа 2010

Пожалуйста, смотрите код ниже. Я ожидаю, что он напечатает либо 10, потому что я явно вызвал сборщик мусора. Но я всегда получаю 0 или 20 в качестве вывода. Почему это так?

void Main()
{
    Panda[] forest_panda = new Panda[10];
    for(int i=0; i<forest_panda.GetLength(0);i++)
    {
        forest_panda[i]=new Panda("P1");
    }

    for(int i=0; i<forest_panda.GetLength(0);i++)
    {
        forest_panda[i]=new Panda("P1");
    }

    System.GC.Collect();

    Console.WriteLine("Total Pandas created is {0}",Panda.population);          
}

class Panda
{
    public static int population=0;
    public string name;

    public Panda(string name)
    {
        this.name = name;
        population = population + 1;
    }

    ~Panda()
    {
        population = population - 1;
    }   
}

Обратите внимание, что класс для Main автоматически создается LINQPad (редактор, который поставляется с книгой "C # 4.0 в двух словах"). Я новичок в C #.

Ответы [ 7 ]

7 голосов
/ 17 августа 2010

Вы не запустили явную сборку мусора. Из документов GC.Collect () :

Используйте этот метод, чтобы попытаться восстановить вся память, которая недоступна. Однако метод Collect не гарантировать, что вся недоступная память исправлен.

Все объекты, независимо от того, как долго они были в памяти, являются рассматривается для сбора; тем не мение, объекты, на которые есть ссылки в управляемых код не собраны. Использовать этот метод заставить систему попытаться вернуть максимальное количество доступная память.

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

У вас есть два варианта сейчас. Вы можете вызвать GC.WaitForPendingFinalizers () , который остановит текущий поток, пока все финализуемые объекты не будут завершены. Или вызовите эту новую перегрузку: System.GC.Collect (создание int, режим System.GCCollectionMode) с GCCollectionMode.Forced Он был представлен в .NET 3.5.

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

Все это определенно не нужно в вашем примере. На самом деле вы хотите IDisposable для детерминированной очистки.

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

Здесь следует отметить пару вещей:

Прежде всего, сборщик мусора ведет себя по-разному между сборками выпуска и отладки.Как правило, в режиме выпуска объекты могут быть восстановлены раньше, чем в режиме отладки.

Как указывает Тим, вызов GC.Collect не вызывает финализаторы.Если вы хотите дождаться запуска финализаторов, также вызовите GC.WaitForPendingFinalizers.

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

3 голосов
/ 17 августа 2010

Попробуйте добавить System.GC.WaitForPendingFinalizers после сбора мусора.http://www.developer.com/net/csharp/article.php/3343191/C-Tip-Forcing-Garbage-Collection-in-NET.htm

2 голосов
/ 17 августа 2010

Вы создаете двадцать объектов, поэтому значение будет равно 20. Явный вызов System.GC.Collect () на самом деле не гарантирует вызова деструктора.Поэтому, если он был вызван, все 20 объектов могли быть разрушены или ни один из них не был.

Это объясняет, что на самом деле происходит.

Не рекомендуется создавать деструктор или вызывать GC.Collect явно.

Если объект должен выполнить очистку, он должен реализовать IDisposable

1 голос
/ 17 августа 2010

Лучше всего использовать шаблон утилизации .Вот полная реализация вашей работы.

Помните, что вы должны вызывать Dispose самостоятельно, как это сделано в приведенном ниже коде.

    public static void Main()
    {
        Panda[] forest_panda = new Panda[10];
        for (int i = 0; i < forest_panda.GetLength(0); i++)
            forest_panda[i] = new Panda("P1");

        // Dispose the pandas by your own
        foreach (var panda in forest_panda)
            panda.Dispose();


        for (int i = 0; i < forest_panda.GetLength(0); i++)
            forest_panda[i] = new Panda("P1");

        // Dispose the pandas by your own
        foreach (var panda in forest_panda)
            panda.Dispose();

        Console.WriteLine("Total Pandas created is {0}", Panda.population);
    }

    class Panda : IDisposable
    {
        public static int population = 0;
        public string name;

        public Panda(string name)
        {
            this.name = name;
            population = population + 1;
        }

        ~Panda()
        {
            Dispose(false);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                population = population - 1;
            }
        }
    }
1 голос
/ 17 августа 2010

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

Вместо этого, если вам нужно явно освободить ресурсы, принадлежащие объекту, вы должны реализовать IDisposable и поместите логику очистки в метод Dispose ().И наоборот, когда вы используете объект, который реализует IDisposable, вы всегда должны позаботиться о вызове его метода Dispose (), когда закончите с ним.C # предоставляет «использование» для этой цели.

Многие классы, которые выполняют ввод / вывод (например, Streams), реализуют IDisposable.Вот пример использования FileStream для чтения текстового файла.Обратите внимание на выражение «using», чтобы убедиться, что FileStream удаляется после того, как мы закончили с ним:

using (FileStream fs = File.OpenRead("C:\\temp\\myfile.txt"))
{
    // Read a text file 1024 bytes at a time and write it to the console
    byte[] b = new byte[1024];
    while (fs.Read(b, 0, b.Length) > 0)
    {
        Console.WriteLine(Encoding.UTF8.GetString(b));
    }
} // Dispose() is called automatically here

Вышеуказанный код эквивалентен этому:

FileStream fs = File.OpenRead("C:\\temp\\myfile.txt"))
try
{
    // Read a text file 1024 bytes at a time and write it to the console
    byte[] b = new byte[1024];
    while (fs.Read(b, 0, b.Length) > 0)
    {
        Console.WriteLine(Encoding.UTF8.GetString(b));
    }
}
finally
{
    fs.Dispose();
}
1 голос
/ 17 августа 2010

В .NET время жизни объекта недетерминировано и не ведет себя так, как вы ожидаете от конструктора / деструктора C ++. Фактически, объекты .NET технически не имеют деструкторов. Финализатор отличается тем, что ожидается очистка неуправляемых ресурсов, используемых объектом в течение срока его службы.

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

class Panda : IDisposable
{
    public static int population = 0;
    public string _name;

    public Panda( string name )
    {
        if( name == null )
            throw new ArgumentNullException( name );

        _name = name;
        population++;
    }

    protected virtual void Dispose( bool disposing )
    {
        if( disposing && name != null )
        {
            population--;
            name = null;
        }
    }

    public void Dispose()
    {
        Dispose( true );
        GC.SuppressFinalize( this );
    }

    ~Panda(){ Dispose( false ); }
}

Затем использовать класс:

using( var panda = new Panda( "Cute & Cuddly" ) )
{
    // Do something with the panda

} // panda.Dispose() called automatically
...