Проблема Разрушения Класса - PullRequest
7 голосов
/ 27 марта 2010

Я делаю простой класс, который содержит 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 уже был удален?

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

Спасибо!

Ответы [ 3 ]

16 голосов
/ 27 марта 2010

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

Это начинается с того, что сначала нужно выделить ресурс операционной системы в своем собственном коде. Это всегда требует какой-то 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 закрыть дескриптор файла.

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

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

5 голосов
/ 27 марта 2010

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

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

Вместо этого внедрить IDisposable.

2 голосов
/ 27 марта 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...