使用LINQ更新字符串数组

Mar*_*s K 5 .net c# linq linq-to-objects

我正在尝试使用LINQ创建一个使用新版本字符串更新AssemblyInfo文件的方法.我可以成功提取我需要更新的字符串,但不知道如何更新集合中的项目.我是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);
    }
Run Code Online (Sandbox Code Playgroud)

Mer*_*ham 6

我是LINQ的新手,所以任何建议都表示赞赏!

Linq主要是围绕查询,聚合和过滤数据而设计的,而不是直接修改现有数据.您可以使用它来制作数据的修改副本,并使用此副本覆盖原始数据.

但是你遇到的问题不完全是因为Linq.

让我们看看你的代码:

string[] asmInfo = File.ReadAllLines(file);
Run Code Online (Sandbox Code Playgroud)

您正在将文件的所有行复制到内存中的字符串数组中.

var line = asmInfo.Single(x => x.Trim().StartsWith("[assembly: AssemblyVersion"));
Run Code Online (Sandbox Code Playgroud)

您正在查询所有行,并将该行(或多或少按值)复制到变量中line.

line = "[assembly: AssemblyVersion\"" + version + "\")]";
Run Code Online (Sandbox Code Playgroud)

您正在使用不同的内容书写复制的行.这不会修改原始数组.

File.WriteAllLines(file, asmInfo);
Run Code Online (Sandbox Code Playgroud)

您正在将原始数组写回文件.您没有更改数组,因此您不会在文件中看到任何更改.

解决这个问题的几种方法:

  • 复制数组,就像您当前所做的那样,找到要更改的行的索引,并修改数组(按索引).此选项根本不使用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);
Run Code Online (Sandbox Code Playgroud)

假设您的文件很小,这是完成任务的一种相当标准的方法.

查询并制作数据的转换副本,并写出副本

这种方法更像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());
Run Code Online (Sandbox Code Playgroud)

这比前一个例子更像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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这需要更多代码,因为:

  • 数据的中间副本现在位于临时文件中,而不是内存中的数组.
  • 流类本身不提供逐行枚举器,因此我们自己构建它们.

如果在调试器中运行它,你会看到一些有趣的东西,并在yield return linewriter.WriteLine语句上设置断点.读取代码和写入代码(或多或少)同时运行.首先读取一行,然后写入.

这是因为IEnumerableyield return,这是Linq不仅仅是语法糖的主要原因之一.