Удаление файлового исключения, удаление строки текста

Я пытаюсь реализовать некоторые базовые функциональные возможности чтения/записи/редактирования файла конфигурации в классе. Файл конфигурации хранится в файле [...]\config.txt, который хранится в переменной 'path'.

Синтаксис моего файла конфигурации следующий:

param0 value_of_it
param1 value_of_it
something_else you_get_it

Все, что осталось от первого пробела, является именем, по которому нужно найти параметр, остальная часть строки считается значением этого параметра.

Так много для контекста. Теперь, когда я пытаюсь удалить этот файл с помощью file.delete, я получаю:

«System.IO.IOException: невозможно получить доступ к файлу, так как он используется другим процессом».

Вот рассматриваемый код:

    internal void alter(string param, bool replace = false, string value = null) {
    //here
        string buf;

        System.IO.StreamReader reader = new System.IO.StreamReader(path);

        string bufPath = path.Substring(0, path.Length-4)+"_buf.txt";

        System.IO.StreamWriter writer = new StreamWriter(bufPath);

        while((buf = reader.ReadLine()) != null) {
            if(buf.Substring(0, param.Length) != param)
                writer.WriteLine(buf);
            else if(replace)
                writer.WriteLine(param+" "+value);
        }
        writer.Dispose();
        reader.Dispose();
        writer.Close();
        reader.Close();
        //there



        File.Delete(path);
        File.Move(bufPath, path);
    }

Функция построчно копирует исходный файл в буфер, кроме параметра, указанного в вызове. Эта строка либо игнорируется, то есть удаляется, либо заменяется, если об этом говорится в вызове. Затем исходный файл удаляется и туда "переносится" (переименовывается) версия _buf.

Эти шаги выполняются правильно (файл _buf создается и правильно заполняется) до «File.Delete (путь)», где возникает исключение.

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

Я попытался найти процесс с проблемным дескриптором с помощью Sysinternals Process Explorer, как было предложено здесь, на ServerFault, но ничего не нашел. Я нашел только три дескриптора для этого файла в своем собственном процессе. Я также запустил приложение после того, как убил проводник Windows, потому что я читал, что иногда это преступник; Это тоже не помогло.

Я также проверил всю остальную часть моей программы и подтвердил следующее:

  1. Этот файл используется впервые, поэтому забытые дескрипторы из прошлого невозможны.

  2. Это однопоточное приложение (пока что), поэтому взаимоблокировки и условия гонки также исключены.

Итак, вот мой вопрос: как я могу найти то, что блокирует мой файл? В качестве альтернативы: есть ли более простой способ удалить или отредактировать строку в файле?


person Mark    schedule 06.01.2014    source источник
comment
Об использовании «использования»: я уже подумал об этом и, вероятно, применю его, как только это будет решено. А пока: даже если я использую оператор «using»; Если я не узнаю, что блокирует этот файл, я никогда не смогу сделать то, что намереваюсь, независимо от того, получу ли я исключение (до сих пор мой способ) или просто ничего не будет сделано («используя» способ) .   -  person Mark    schedule 06.01.2014
comment
Сначала Close, потом Dispose (не иначе). Это наверное причина.   -  person Sinatr    schedule 06.01.2014
comment
@Sinatr: к сожалению, не помогает. Уже оставил всю часть чтения/записи и просто попробовал File.Delete(path) с тем же результатом: -/   -  person Mark    schedule 06.01.2014
comment
ваш текущий код здесь работает нормально, я только что попробовал. Возможно, у вас есть блокнот или любая другая программа для доступа к файлу, я не уверен. В любом случае, у вас есть лучший вариант для хранения файла конфигурации. Вы можете использовать формат xml, см. мой ответ, как это сделать.   -  person har07    schedule 06.01.2014


Ответы (2)


Будет удобнее хранить конфигурацию в формате xml, потому что у вас есть возможность сохранить более сложное значение, если вам нужно будет сделать это позже. А также вы можете положиться на XDocument для загрузки и сохранения файла так же просто, как XDocument.Load(path) и doc.Save(path). Например, ваш текущий файл конфигурации будет выглядеть следующим образом в формате xml:

<?xml version="1.0" encoding="utf-8"?>
<params>
  <param name="param0">value_of_it</param>
  <param name="param1">value_of_it</param>
  <param name="something_else">you_get_it</param>
</params>

И ваша функция для изменения существующего значения параметра или добавления нового параметра в файл конфигурации будет выглядеть так:

internal void alterXml(string param, bool replace = false, string value = null)
{
    //load xml configuration file to XDocument object
    var doc = XDocument.Load(path);
    //search for <param> having attribute "name" = param
    var existingParam = doc.Descendants("param").FirstOrDefault(o => o.Attribute("name").Value == param);
    //if such a param element doesn't exist, add new element
    if (existingParam == null)
    {
        var newParam = new XElement("param");
        newParam.SetAttributeValue("name", param);
        newParam.Value = "" + value;
        doc.Root.Add(newParam);
    }
    //else update element's value
    else if (replace) existingParam.Value = "" + value;
    //save modified object back to xml file
    doc.Save(path);
}
person har07    schedule 06.01.2014
comment
Так что это похоже на то, что предлагает Конор Стоукс выше, но с меньшим количеством руководств. Я загружаю файл в память, делаю все, что хочу, а затем сохраняю его. Я тоже попробую. Однако я не так хорошо знаком с XML, так что посмотрим на результаты. Какое пространство имен я могу найти? - person Mark; 06.01.2014
comment
XDocument находится в пространстве имен System.Xml.Linq. Вы не пожалеете, что попробовали этот, базовые знания xml, вероятно, помогут вам во многих других сценариях позже :) - person har07; 06.01.2014
comment
Единственные варианты, которые я получаю для using System.Xml.[...], - это схема, сериализация, Xsl и XPath. Может ли это быть связано с тем, что я ограничен .NET 3.0? Также я не использую VS, а использую Sharp-Develop. - person Mark; 06.01.2014
comment
System.Xml.Linq находится в другой сборке, чем System.Xml. Вам нужно добавить using System.Xml.Linq; - person har07; 06.01.2014
comment
Я знаю, но моя IDE не предлагает мне этого и все равно жалуется, когда я его набираю Тип или имя пространства имен «Linq» не существует в пространстве имен «System.Xml» (вам не хватает ссылки на сборку?) ( CS0234) - person Mark; 06.01.2014
comment
проверьте ссылки на свои проекты, добавьте ссылку на System.Xml.Linq.dll, если ее еще нет. - person har07; 06.01.2014
comment
Нашел проблему: как я добавил к предыдущему комментарию: я ограничен .NET 3.0, что, по-видимому, и является проблемой. Я только что обновил свой проект до версии 4.5, и теперь доступно пространство имен Linq. Наконец, веские причины убить это глупое требование сделать так, чтобы оно могло работать на .net 3 :-D - person Mark; 06.01.2014
comment
Сейчас я разобрался с XML, и вы правы: он намного лучше и стоил исследования. Теперь я могу полностью затормозить с несколькими слоями и уровнями. Палец вверх, снова! (: - person Mark; 07.01.2014

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

Поскольку файл по пути открыт в режиме чтения, его без проблем смогут открыть другие читатели. Однако, как только другой код попытается выполнить операцию записи (включая метаданные файла, например File.Delete), вы увидите эту ошибку.

Ваш код не является безопасным для исключений, поэтому исключение, вызванное этой или подобной функцией, когда вы читаете из потока в этом цикле, приведет к тому, что дескриптор останется открытым, что приведет к сбою более позднего вызова функции, которая открывает файл с исключение, которое вы сейчас испытываете в File.Delete. Чтобы избежать проблем с безопасностью исключений, используйте try/finally или using (ниже я приведу пример).

Одним из таких исключений, которое может возникнуть, является вызов Substring внутри цикла, поскольку есть вероятность, что длина строки меньше длины полного параметра. Здесь также есть еще одна проблема, заключающаяся в том, что если вы передаете параметр без завершающего пробела, то возможно, что вы совпадете с другим параметром, который содержит первый в качестве префикса (т. е. «hello» будет соответствовать «hello_world»).

Вот слегка исправленная версия кода, которая безопасна для исключений и устраняет проблему с подстрокой.

internal void alter( string param, bool replace = false, string value = null )
{
    //here
    string buf;
    string bufPath = path.Substring( 0, path.Length - Path.GetExtension( path ).Length ) + "_buf.txt";

    using ( System.IO.StreamReader reader = new System.IO.StreamReader( path ) )
    {
        string paramWithSpace = param + " ";

        using ( System.IO.StreamWriter writer = new StreamWriter( bufPath ) )
        {
            while ( ( buf = reader.ReadLine() ) != null )
            {
                if ( !buf.StartsWith( paramWithSpace ) )
                    writer.WriteLine( buf );
                else if ( replace )
                    writer.WriteLine( paramWithSpace + value );
            }
        }
    }
        //there

    File.Delete( path );
    File.Move( bufPath, path );
}

Тем не менее, вы можете захотеть полностью загрузить свою конфигурацию в память, изменить ее в памяти несколько раз, а затем выполнить обратную запись в нее один раз в пакете. Это повысит производительность при чтении/записи конфигурации (и обычно вам придется вносить сразу несколько изменений конфигурации), а также упростит ваш код. Для простой реализации используйте универсальный класс Dictionary.

person Conor Stokes    schedule 06.01.2014
comment
Я немного больше исследовал дескрипторы-призраки: все они имеют одинаковый PID в соответствии с проводником процессов, что еще больше меня смутило. Что касается приветствия, которое также можно найти в hello_world: на самом деле я использовал это в прошлой версии и забыл об этом по дороге сюда снова, плюс ваш вариант более элегантный и, вероятно, более производительный, чем мой. Я пытаюсь сделать все это в памяти сейчас. Я буду читать исходный файл построчно в словарь, делать манипуляции, а затем записывать их обратно, когда закончу. Спасибо уже! - person Mark; 06.01.2014