使用LINQ更新字符串数组

我正在尝试使用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); } 

我是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); 

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

解决此问题的几种方法:

  • 像当前一样复制出数组,找到要更改的行的索引,并修改数组(按索引)。 此选项根本不使用Linq。
  • 使用linq创建数据的转换副本,并将整个转换后的副本写回。 此选项使用linq,但不利用其function。
  • 停止使用File.ReadAllLines / File.WriteAllLines ,并一次读/写一行。 这个选项实际上利用了Linq的强大function,但却是最复杂的。

修改数组

这个方法根本不使用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 。 因此,您被迫致电ToArray 。 当您调用ToArray (或ToList )时,将运行整个查询并将其复制到新数组中,因此您可以使用该文件的两个副本。

如果File.WriteAllLines采用了IEnumerable ,那么您不必再创建一个额外的副本,并且在查询仍在运行时会写入每一行。

一次读写一行

这个选项是最有效的,实际上显示了Linq的一些好处 – 更具体地说,是IEnumerable一些好处,这是Linq的大部分function来自的地方。

 public static class FileStreamExtensions { public static IEnumerable 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 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 linewriter.WriteLine语句中设置断点。 读取代码和写入代码(或多或少)同时运行。 首先读取一行,然后写入。

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

我建议使用正则Regex

 private static void UpdateVersion(string file, string version) { var fileContent = File.ReadAllText(file); var newFileContent = Regex.Replace( fileContent, @"(?<=AssemblyVersion\("")(?.*?)(?=""\))", 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()); } 

您可能不应该尝试使用LINQ来修改原始查询源,因为它很笨拙且容易出错,而不是LINQ的“样式”。

相反,您可以使用LINQ作为filter来创建数组。 我们需要做的就是重新安排你的代码:

 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); }