У меня есть класс, который использует временные файлы (Path.GetTempFileName()
) в то время как это активно. Я хочу удостовериться, что эти файлы не остаются на месте занимающего жесткого диска пользователя после того, как моя программа закрывается. Прямо сейчас мой класс имеет a Close()
метод, который проверяет, существуют ли какие-либо временные файлы, используемые классом все еще, и удаляют их.
Имело бы больше смысла вставлять этот код Расположение () или Завершать () методы вместо этого?
Еще лучше было бы создать файл с FileOptions.DeleteOnClose
. Это гарантирует, что операционная система принудительно удалит файл при завершении вашего процесса (даже в случае грубого прерывания). Конечно, вы все равно захотите закрыть/удалить файл самостоятельно, когда закончите работу с ним, но это обеспечит хороший запасной вариант, чтобы не позволить файлам сидеть вечно
Я бы сделал и то, и другое; сделайте класс одноразовым и попросите финализатор очистить его. Существует стандартный шаблон для того, чтобы сделать это безопасно и эффективно: используйте его вместо того, чтобы пытаться вывести для себя, какой шаблон является правильным. Ошибиться очень легко. Прочтите внимательно :
http://msdn.microsoft.com/en-us/library/system.idisposable.aspx
Обратите внимание, что вы должны быть действительно действительно осторожно при написании финализатора. Когда запускается финализатор, многие из ваших обычных предположений неверны:
Существуют всевозможные варианты состояний гонки или тупиковых ситуаций, потому что вы больше не в основном потоке, вы находитесь в потоке финализатора.
В обычном коде, если вы запускаете код внутри объекта, вы знаете, что все вещи, на которые ссылается объект, живы. В финализаторе все вещи, на которые ссылается объект, могли быть только что завершены! Финализаторы мертвых объектов могут запускаться в любом порядке, включая финализацию «дочерних» объектов перед «родительскими» объектами.
В обычном коде назначение ссылки на объект статическому полю может быть вполне разумным. В финализаторе ссылка, которую вы назначаете, может быть на уже мертвый объект , и поэтому назначение возвращает мертвый объект к жизни.(Потому что объекты, на которые ссылаются статические поля, всегда живы.) Это чрезвычайно странное состояние, и в этом случае ничего приятного не происходит.
И так далее. Будьте осторожны. Ожидается, что вы полностью поймете работу сборщика мусора, если напишете нетривиальный финализатор.
Вы определенно должны использовать Dispose
для очистки ресурсов, но убедитесь, что вы реализуете интерфейс IDisposable
. Не стоит просто добавлять метод с именем Dispose
.
Файл - это неуправляемый ресурс, и вы реализуете IDisposable для очистки неуправляемых ресурсов, от которых зависят ваши классы.
Я реализовал подобные классы, хотя никогда не использовал их в производственном коде.
Однако я понимаю вашу осторожность в этом вопросе - взаимодействие пользователя с файлами вне вашего приложения может испортить ситуацию и вызвать проблемы при удалении. Однако это относится к любому файлу, созданному/удаленному приложением, независимо от того, приводится ли он в порядок методом Dispose() или нет.
Я бы сказал, что реализация IDisposable была бы разумным выбором.
Хороший способ предлагает Дэвид М. Кин в записи MSDN о Path.GetTempFileName
. Он создает класс-обертку, реализующий IDisposable
, который будет автоматически удалять файл:
public class TemporaryFile : IDisposable
{
private bool _isDisposed;
public bool Keep { get; set; }
public string Path { get; private set; }
public TemporaryFile() : this(false)
{
}
public TemporaryFile(bool shortLived)
{
this.Path = CreateTemporaryFile(shortLived);
}
~TemporaryFile()
{
Dispose(false);
}
public void Dispose()
{
Dispose(false);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
_isDisposed = true;
if (!this.Keep)
{
TryDelete();
}
}
}
private void TryDelete()
{
try
{
File.Delete(this.Path);
}
catch (IOException)
{
}
catch (UnauthorizedAccessException)
{
}
}
public static string CreateTemporaryFile(bool shortLived)
{
string temporaryFile = System.IO.Path.GetTempFileName();
if (shortLived)
{
// Set the temporary attribute, meaning the file will live
// in memory and will not be written to disk
//
File.SetAttributes(temporaryFile,
File.GetAttributes(temporaryFile) | FileAttributes.Temporary);
}
return temporaryFile;
}
}
Использовать новый класс легко, просто введите следующее:
using (TemporaryFile temporaryFile = new TemporaryFile())
{
// Use temporary file
}
Если после создания TemporaryFile вы решите, что хотите предотвратить его удаление, просто установите свойство TemporaryFile.Keep в true:
using (TemporaryFile temporaryFile = new TemporaryFile())
{
temporaryFile.Keep = true;
}
Если вы хотите повторно использовать свои временные файлы, например open \ close \ read \ write \ etc, тогда может быть полезно очистить их на уровне выгрузки AppDomain.
Это можно использовать в сочетании с помещением временных файлов в хорошо известный подкаталог временного местоположения и проверкой того, что каталог удаляется при запуске приложения, чтобы предотвратить нечистые отключения.
Базовый пример техники (с удалением обработки исключений вокруг удаления для краткости). Я использую эту технику в файловых модульных тестах, где это имеет смысл и полезно.
public static class TempFileManager
{
private static readonly List<FileInfo> TempFiles = new List<FileInfo>();
private static readonly object SyncObj = new object();
static TempFileManager()
{
AppDomain.CurrentDomain.DomainUnload += CurrentDomainDomainUnload;
}
private static void CurrentDomainDomainUnload(object sender, EventArgs e)
{
TempFiles.FindAll(file => File.Exists(file.FullName)).ForEach(file => file.Delete());
}
public static FileInfo CreateTempFile(bool autoDelete)
{
FileInfo tempFile = new FileInfo(Path.GetTempFileName());
if (autoDelete)
{
lock (SyncObj)
{
TempFiles.Add(tempFile);
}
}
return tempFile;
}
}
Я всегда делаю свои классы, которые указывают на временные файлы IDisposable
, и обычно реализую финализатор, который вызывает мой метод dispose там же. Это похоже на парадигму, предложенную на IDisposable
странице MSDN.
Связанный код ниже:
public void Dispose()
{
Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
private void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if(!this.disposed)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if(disposing)
{
// Dispose managed resources.
}
// Call the appropriate methods to clean up
// unmanaged resources here.
// If disposing is false,
// only the following code is executed.
// Note disposing has been done.
disposed = true;
}
}
// Use C# destructor syntax for finalization code.
// This destructor will run only if the Dispose method
// does not get called.
// It gives your base class the opportunity to finalize.
// Do not provide destructors in types derived from this class.
~MyResource()
{
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
Dispose(false);
}
Absolutely. Так вы сможете обеспечить очистку при наличии исключений.