【问题标题】:Using LINQ to update string array使用 LINQ 更新字符串数组
【发布时间】:2011-06-18 05:51:04
【问题描述】:

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

【问题讨论】:

    标签: c# .net linq linq-to-objects


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

      我建议为此使用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());
      }
      

      【讨论】:

        【解决方案3】:

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

        相反,您可以将 LINQ 用作 过滤器 来创建 new 数组。我们需要做的就是重新排列你的代码:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-03-02
          • 2012-03-30
          • 1970-01-01
          • 1970-01-01
          • 2016-09-21
          相关资源
          最近更新 更多