Почему File.Exists и Directory.Exists не следует использовать?
Например использовать так:
if (File.Exists(path))
{
using var fs = File.OpenRead(path);
// ... чтение файла
}
Или так:
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
Таких случаев много попадается в разных проектах повсеместно, но что в этом плохого или опасного?
А может есть случаи, когда эти методы можно и нужно использовать? Не просто же так они существуют в API .NET.
Ответы (1 шт):
Эти методы действительно очень часто являются индикаторами неоптимизированного или ненадёжного кода и несут в себе сразу несколько проблем:
- Нарушение потокобезопасности при обращении к файловой системе - об этом уже есть здесь ответ
- Лишнее обращение к файловой системе
- Как результат - потеря производительности кода
Наибольшую опасность File.Exists и Directory.Exists представляют в многопоточной среде, особенно на серверах ASP.NET Core, где с файлами иногда приходится работать интенсивно, а запросы к серверу одновременно могут выполнять десятки тысяч пользователей.
Что касается потокобезопасности, то повторю слова из ответа по ссылке выше: между проверкой существования и последующей операцией чтения/записи может пройти время, и в это время другой поток или приложение что-то сделает с файлом и каталогом, и выполняемый дальше код попросту упадёт.
Когда мы используем этим методы - система обращается к диску, а следовательно тратит на это время и даёт лишнюю нагрузку на файловую систему.
Что же делать в таком случае:
try
{
using var fs = File.OpenRead(path);
// ... чтение файла
}
catch (FileNotFoundException)
{
// ... файл не найден
}
Что касается Directory.Exists, то создавать каталог можно просто вот так:
Directory.CreateDirectory(path);
Если обратиться к документации по методу Directory.CreateDirectory:
Создает все каталоги и подкаталоги по указанному пути, если они еще не существуют.
То есть если каталог уже есть, ничего не сломается, метод просто ничего не будет делать. Поэтому проверка на существование каталога не требуется.
Ну и ответ на последний вопрос, когда же всё-таки можно и нужно использовать .Exists? Тогда, когда кроме как проверить наличие файла или каталога и вне зависимости от его наличия дальше с ним ничего делать не требуется. Проверили и всё.
И напоследок маленький тест.
Пусть я хочу в многопоточном режиме писать файл из разных приложений, поэтому lock буду создавать в виде файла, и если файл залочен, то буду снова проверять лок до тех пор, пока не освободится, и только затем писать данные.
if (!File.Exists(lockName))
{
using (var fs = File.Create(lockName)) { }
File.WriteAllBytes(fileName, randomBytes);
File.Delete(lockName);
}
Нормальный же код с виду, верно? Нет, не нормальный, давайте его проверим вот так:
internal class Program
{
private const string fileName = "file.bin";
private const string lockName = "file.lock";
private static byte[] randomBytes = new byte[10000];
static async Task Main(string[] args)
{
Random.Shared.NextBytes(randomBytes);
List<Task<bool>> tasks = [];
for (int i = 0; i < 100; i++)
{
tasks.Add(Task.Run(WriteFileSafe));
}
var result = await Task.WhenAll(tasks);
Console.WriteLine();
Console.WriteLine($"Done. Errors count: {result.Count(x => !x)}");
}
private static bool WriteFileSafe()
{
try
{
while (true)
{
if (!File.Exists(lockName))
{
using (var fs = File.Create(lockName)) { }
File.WriteAllBytes(fileName, randomBytes);
File.Delete(lockName);
Console.Write("+");
return true;
}
}
}
catch
{
Console.Write("!");
return false;
}
}
}
То есть метод пытается записать в файл до тех пор, пока не получится записать или не выбросится исключение. Запускаю:
+!!!+!+!++!!!+!!!!!++!!+!!!!!+!++!!+!!!!!!+!++!+!!!!++!!++++!!!!!++++!!+!!!+++!+++!!!++!+!+!!!!!+!!!
Done. Errors count: 61
Ого, сколько ошибок. Ну теперь давайте починим:
while (true)
{
try
{
using var fs = File.Create(lockName, 4096, FileOptions.DeleteOnClose);
try
{
File.WriteAllBytes(fileName, randomBytes);
}
catch
{
Console.Write("w");
return false;
}
}
catch (IOException)
{
continue;
}
Console.Write("+");
return true;
}
Изменился только код внутри бесконечного цикла, запускаю:
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Done. Errors count: 0
Здесь я добавил дополнительную проверку, что во время записи файла с данными тоже нет проблем. Как видим, работает, а если дополнительную проверку убрать, то и кода стало меньше. Это я всё к тому, что старайтесь находить решения, чтобы писать надёжный код. Порой самый очевидный код таким не является.