Использование LINQ для обновления массива строк - PullRequest
5 голосов
/ 18 июня 2011

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

private void UpdateVersion(string file, string version)
    {
        string[] asmInfo = File.ReadAllLines(file);
        var line = asmInfo.Single(x => x.Trim().StartsWith("[assembly: AssemblyVersion"));
        line = "[assembly: AssemblyVersion\"" + version + "\")]";

        // This doesn't work - it's writing the original file back
        File.WriteAllLines(file, asmInfo);
    }

Ответы [ 3 ]

6 голосов
/ 18 июня 2011

Я новичок в LINQ, поэтому любые советы приветствуются!

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

Однако проблема, с которой вы столкнулись, не совсем из-за Linq.

Давайте рассмотрим ваш код:

string[] asmInfo = File.ReadAllLines(file);

Вы копируете все строки файла в массив строк в памяти.

var line = asmInfo.Single(x => x.Trim().StartsWith("[assembly: AssemblyVersion"));

Вы запрашиваете все строки,и копирование строки (более или менее по значению) в переменную line.

line = "[assembly: AssemblyVersion\"" + version + "\")]";

Вы переписываете скопированную строку с другим содержимым.Это не изменяет исходный массив.

File.WriteAllLines(file, asmInfo);

Вы записываете исходный массив в файл.Вы не изменили массив, поэтому вы не увидите никаких изменений в файле.

Несколько способов решить эту проблему:

  • Скопируйте массив, как выв настоящее время, найдите индекс строки, которую вы хотите изменить, и измените массив (по индексу).Эта опция вообще не использует Linq.
  • Используйте linq, чтобы сделать преобразованную копию данных, и записать всю преобразованную копию обратно.Эта опция использует linq, но не использует ее мощность.
  • Прекратите использовать File.ReadAllLines / File.WriteAllLines и читайте / пишите по одной строке за раз.Эта опция на самом деле использует мощь Linq, но является наиболее сложной.

Изменить массив

Этот метод вообще не использует Linq:

string[] asmInfo = File.ReadAllLines(file);
int lineIndex = Array.FindIndex(asmInfo,
    x => x.Trim().StartsWith("[assembly: AssemblyVersion"));
asmInfo[lineIndex] = "[assembly: AssemblyVersion\"" + version + "\")]";
File.WriteAllLines(file, asmInfo);

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

Запросите и сделайте преобразованную копию данных и запишите копию

Этот метод более похож на Linq:

string[] asmInfo = File.ReadAllLines(file);
var transformedLines = asmInfo.Select(
    x => x.Trim().StartsWith("[assembly: AssemblyVersion")
        ? "[assembly: AssemblyVersion\"" + version + "\")]"
        : x
    );
File.WriteAllLines(file, asmInfo.ToArray());

Этот метод более похож на Linq, чем в предыдущем примере.Но на самом деле он менее эффективен, потому что File.WriteAllLines не может взять IEnumerable<string>.Из-за этого вы вынуждены звонить ToArray.Когда вы вызываете ToArray (или ToList), весь запрос запускается и копируется в новый массив, так что у вас есть две копии файла.

Если File.WriteAllLines занял IEnumerable<string>, тогда вам не нужно будет делать дополнительную копию, и каждая строка будет записываться, пока запрос еще выполняется.

Чтение и запись по одной строке за раз

Этот вариант является наиболее эффективным и фактически демонстрирует некоторые преимущества Linq, в частности, IEnumerable, из которого исходит большая часть мощности Linq.

public static class FileStreamExtensions
{
    public static IEnumerable<string> GetLines(this FileStream stream)
    {
        using (var reader = new StreamReader(stream))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
                yield return line;
        }
    }

    public static void WriteLines(this FileStream stream, IEnumerable<string> lines)
    {
        using (var writer = new StreamWriter(stream))
        {
            foreach (string line in lines)
            {
                writer.WriteLine(line);
            }
        }
    }
}

private void UpdateVersion(string file, string version)
{
    using (FileStream inputFile = File.OpenRead(file))
    {
        string tempFilePath = Path.GetTempFileName();

        var transformedLines = inputFile.GetLines().Select(
            x => x.Trim().StartsWith("[assembly: AssemblyVersion")
                ? "[assembly: AssemblyVersion\"" + version + "\")]"
                : x
            );

        using (FileStream outputFile = File.OpenWrite(tempFilePath))
        {
            outputFile.WriteLines(transformedLines);
        }

        string backupFilename = Path.Combine(Path.GetDirectoryName(file), Path.GetRandomFileName());
        File.Replace(tempFilePath, file, backupFilename);
    }
}

Для этого требуетсянамного больше кода, потому что:

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

Вы увидите некоторые интересные вещи, если запустите его в отладчике и установите точки останова в операторах yield return line и writer.WriteLine,Код чтения и код записи (более или менее) выполняются одновременно.Сначала читается строка, затем пишется.

Это из-за IEnumerable и yield return, и это одна из главных причин, почему Linq - это больше, чем просто синтаксический сахар.

3 голосов
/ 18 июня 2011

Я предлагаю использовать Regex для этого:

private static void UpdateVersion(string file, string version)
{
    var fileContent = File.ReadAllText(file);
    var newFileContent = Regex.Replace(
        fileContent,
        @"(?<=AssemblyVersion\("")(?<VersionGroup>.*?)(?=""\))",
        version);
    File.WriteAllText(file, newFileContent);
}

Вы все еще можете сделать это с LINQ, но это будет менее эффективно и более подвержено ошибкам, я думаю.

private static void UpdateVersion(string file, string version)
{
    var linesList = File.ReadAllLines(file).ToList();
    var line = linesList.Single(x => x.Trim().StartsWith("[assembly: AssemblyVersion"));
    linesList.Remove(line);
    linesList.Add("[assembly: AssemblyVersion(\"" + version + "\")]");
    File.WriteAllLines(file, linesList.ToArray());
}
1 голос
/ 18 июня 2011

Вы, вероятно, не должны пытаться использовать LINQ для изменения исходного источника запросов, потому что он неудобен и подвержен ошибкам, а не в "стиле" LINQ.

Вместо этого вы можете использовать LINQ как фильтр для создания нового массива.Все, что нам нужно сделать, это немного изменить ваш код:

private void UpdateVersion(string file, string version)
{
    string[] asmInfo = File.ReadAllLines(file);
    asmInfo = asmInfo.Select(x => x.Trim().StartsWith("[assembly: AssemblyVersion") ?
        "[assembly: AssemblyVersion\"" + version + "\")]" : x).ToArray();

    File.WriteAllLines(file, asmInfo);
}
...