C# 使用LINQ更新字符串数组

C# 使用LINQ更新字符串数组,c#,.net,linq,linq-to-objects,C#,.net,Linq,Linq To Objects,我正在尝试创建一个方法,使用LINQ使用新版本字符串更新AssemblyInfo文件。我可以成功提取需要更新的字符串,但不确定如何更新集合中的项。我是LINQ的新手,所以任何建议都非常感谢 private void UpdateVersion(string file, string version) { string[] asmInfo = File.ReadAllLines(file); var line = asmInfo.Single(x =>

我正在尝试创建一个方法,使用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);
    }

我建议为此使用
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来修改原始查询源,因为它既笨拙又容易出错,而且不符合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);
}
我是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);
    }
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,但不利用其功能
  • 停止使用
    文件.ReadAllLines
    /
    文件.writeallines
    ,一次读/写一行。此选项实际上利用了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.writeAllines
不能接受
IEnumerable
。因此,您必须调用
ToArray
。当您调用
ToArray
(或
ToList
)时,整个查询将运行并复制到一个新数组中,因此您有两个文件副本

如果
File.writeAllines
获取了一个
IEnumerable
,那么您就不必额外复制,并且每一行都将在查询仍在运行时写入

一次读写一行

此选项是最有效的,并且实际上显示了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);
    }
}
公共静态类FileStreamExtensions
{
公共静态IEnumerable GetLines(此文件流)
{
使用(变量读取器=新的流读取器(流))
{
弦线;
而((line=reader.ReadLine())!=null)
收益率回归线;
}
}
公共静态void WriteLines(此文件流,IEnumerable行)
{
使用(var writer=新的StreamWriter(流))
{
foreach(行中的字符串行)
{
writer.WriteLine(行);
}
}
}
}
私有void UpdateVersion(字符串文件,字符串版本)
{
使用(FileStream inputFile=File.OpenRead(File))
{
字符串tempFilePath=Path.GetTempFileName();
var transformedLines=inputFile.GetLines()。选择(
x=>x.Trim().StartsWith(“[assembly:AssemblyVersion”)
?“[assembly:AssemblyVersion\”“+version+”\”)”
:x
);
使用(FileStream outputFile=File.OpenWrite(tempFilePath))
{
outputFile.WriteLines(转换行);
}
字符串backupFilename=Path.Combine(Path.GetDirectoryName(文件),Path.GetRandomFileName());
替换(tempFilePath、File、backupFilename);
}
}
这需要更多的代码,因为:

  • 数据的中间副本现在位于临时文件中,而不是内存阵列中
  • 流类本身不提供逐行枚举器,所以我们自己构建它们
如果在调试器中运行它,并在
yield返回行
writer.WriteLine
语句上设置断点,您将看到一些有趣的事情。读代码和写代码(或多或少)同时运行。首先读取一行,然后写入一行

这是因为
IEnumerable
yield-return
,这也是Linq不仅仅是语法糖的主要原因之一