Проблема деструктора класса

Я делаю простой класс, который содержит StreamWrite

    class Logger
{
    private StreamWriter sw;
    private DateTime LastTime; 
    public Logger(string filename)
    {
        LastTime = DateTime.Now;
        sw = new StreamWriter(filename);
    }
    public void Write(string s)
    {
        sw.WriteLine((DateTime.Now-LastTime).Ticks/10000+":"+ s);
        LastTime = DateTime.Now;
    }
    public void Flush()
    {
        sw.Flush();
    }
    ~Logger()
    {
        sw.Close();//Raises Exception!
    }
}

Но когда я закрываю этот StreamWriter в деструкторе, он повышает исключение, что StreamWriter был уже удален?

Почему? И как заставить его работать таким образом, что, когда класс Регистратора удален, StreamWriter закрывается перед удалением?

Спасибо!

7
задан Betamoo 27 March 2010 в 12:19
поделиться

3 ответа

Написание собственного деструктора (также известного как финализатор) неверно в 99,99% всех случаев. Они необходимы, чтобы гарантировать, что ваш класс освободит ресурс операционной системы, который автоматически не управляется платформой .NET и не был должным образом выпущен пользователем вашего класса.

Это начинается с того, что сначала необходимо выделить ресурс операционной системы в вашем собственном коде. Для этого всегда требуется какой-то P / Invoke. Это очень редко требуется, это была работа программистов .NET, работающих в Microsoft.

Так и было в случае StreamWriter. Через несколько слоев это оболочка вокруг дескриптора файла, созданная с помощью CreateFile (). Класс, создавший дескриптор, также отвечает за написание финализатора. Код Microsoft, а не ваш.

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

Конечно, ваш объект StreamWriter является частной деталью реализации вашего класса. Вы не знаете, когда пользователь завершил работу с вашим классом Logger, вы не можете вызвать StreamWriter.Dispose () автоматически. Тебе нужна помощь.

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

  class Logger : IDisposable {
    private StreamWriter sw;
    public void Dispose() {
      sw.Dispose();   // Or sw.Close(), same thing
    }
    // etc...
  }

Что ж, это то, что вы должны делать в 99,9% всех случаев. Но не здесь.Рискуя ввести вас в заблуждение: если вы реализуете IDisposable, у пользователя вашего класса должна быть разумная возможность вызвать его метод Dispose (). Обычно это не проблема, за исключением класса типа Logger. Весьма вероятно, что ваш пользователь захочет что-то записать до последнего момента. Чтобы она могла зарегистрировать необработанное исключение, например, из AppDomain.UnhandledException.

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

У регистратора есть особые требования для правильного выключения во всех случаях. Для этого необходимо установить для свойства StreamWriter.AutoFlush значение true, чтобы выходные данные журнала сбрасывались сразу после записи. Учитывая сложность правильного вызова Dispose (), теперь лучше не реализовывать IDisposable и позволить финализатору Microsoft закрыть дескриптор файла.

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

И последнее, но не менее важное: написать регистратор довольно сложно, как объяснялось выше. Log4net - популярное решение.NLog - лучшая мышеловка, библиотека, которая работает по принципу dotnetty вместо того, чтобы ощущаться как порт Java.

16
ответ дан 6 December 2019 в 07:50
поделиться

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

В качестве дополнительного предостережения, имейте в виду, что Finalizer работает в собственном потоке, что может поставить вас в тупик, если вы используете неуправляемые API, которые имеют потоковую привязку. Эти сценарии гораздо чище с IDisposable и хорошей, упорядоченной очисткой.

2
ответ дан 6 December 2019 в 07:50
поделиться

Деструкторы (также известные как финализаторы) не гарантируют запуск в определенном порядке. См. документацию :

Не гарантируется, что финализаторы двух объектов будут запускаться в каком-либо определенном порядке, даже если один объект ссылается на другой. То есть, если объект A имеет ссылку на объект B и оба имеют финализаторы, объект B мог уже быть завершен при запуске финализатора объекта A.

Вместо этого реализуйте IDisposable .

5
ответ дан 6 December 2019 в 07:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: