【问题标题】:CSV string handlingCSV 字符串处理
【发布时间】:2010-09-05 11:43:31
【问题描述】:

创建CSV 字符串的典型方法(伪代码):

  1. 创建一个 CSV 容器对象(如 C# 中的 StringBuilder)。
  2. 遍历要添加的字符串,并在每个字符串后附加一个逗号。
  3. 在循环之后,删除最后一个多余的逗号。

代码示例:

public string ReturnAsCSV(ContactList contactList)
{
    StringBuilder sb = new StringBuilder();
    foreach (Contact c in contactList)
    {
        sb.Append(c.Name + ",");
    }

    sb.Remove(sb.Length - 1, 1);
    //sb.Replace(",", "", sb.Length - 1, 1)

    return sb.ToString();
}

我喜欢通过检查容器是否为空来添加逗号的想法,但这是否意味着需要更多处理,因为它需要在每次出现时检查字符串的长度?

我觉得应该有一种更简单/更清洁/更有效的方法来删除最后一个逗号。有什么想法吗?

【问题讨论】:

    标签: c# csv


    【解决方案1】:

    您可以改为在 foreach 中添加逗号作为第一件事。

    if (sb.Length > 0) sb.Append(",");

    【讨论】:

      【解决方案2】:

      如何跟踪您是否在第一个项目上,如果不是第一个项目,则仅在该项目之前添加一个逗号

      public string ReturnAsCSV(ContactList contactList)
      {
          StringBuilder sb = new StringBuilder();
          bool isFirst = true;
      
          foreach (Contact c in contactList) {
              if (!isFirst) { 
                // Only add comma before item if it is not the first item
                sb.Append(","); 
              } else {
                isFirst = false;
              }
      
              sb.Append(c.Name);
          }
      
          return sb.ToString();
      }
      

      【讨论】:

        【解决方案3】:

        你可以使用LINQ to Objects:

        string [] strings = contactList.Select(c => c.Name).ToArray();
        string csv = string.Join(",", strings);
        

        显然,这一切都可以在一行中完成,但两行就更清楚了。

        【讨论】:

        • 不太明显的是它实际上并没有实现 CSV 规范。这是将逗号放入字符串的好方法,但这与 CSV 格式不同。
        【解决方案4】:

        我喜欢通过检查容器是否为空来添加逗号的想法,但这是否意味着需要更多处理,因为它需要在每次出现时检查字符串的长度?

        您过早地进行优化,对性能的影响可以忽略不计。

        【讨论】:

          【解决方案5】:

          您还可以创建一个 c.Name 数据数组并使用 String.Join 方法来创建您的线路。

          public string ReturnAsCSV(ContactList contactList)
          {
              List<String> tmpList = new List<string>();
          
              foreach (Contact c in contactList)
              {
                  tmpList.Add(c.Name);
              }
          
              return String.Join(",", tmpList.ToArray());
          }
          

          这可能不如 StringBuilder 方法那么高效,但它看起来确实更简洁。

          另外,您可能需要考虑使用 .CurrentCulture.TextInfo.ListSeparator 而不是硬编码的逗号——如果您的输出要导入其他应用程序,您可能会遇到以下问题它。 ListSeparator 在不同的文化中可能会有所不同,至少 MS Excel 尊重此设置。所以:

          return String.Join(
              System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator,
              tmpList.ToArray());
          

          【讨论】:

            【解决方案6】:

            修剪一下怎么样?

            public string ReturnAsCSV(ContactList contactList)
            {
                StringBuilder sb = new StringBuilder();
            
                foreach (Contact c in contactList)
                {
                    sb.Append(c.Name + ",");
                }
            
                return sb.ToString().Trim(',');
            }
            

            【讨论】:

              【解决方案7】:

              只是一个想法,但请记住在字段值中处理逗号和引号 ("),否则您的 CSV 文件可能会破坏消费者阅读器。

              【讨论】:

                【解决方案8】:

                别忘了我们的老朋友“for”。它不像 foreach 那样好看,但它的优点是可以从第二个元素开始。

                public string ReturnAsCSV(ContactList contactList)
                {
                    if (contactList == null || contactList.Count == 0)
                        return string.Empty;
                
                    StringBuilder sb = new StringBuilder(contactList[0].Name);
                
                    for (int i = 1; i < contactList.Count; i++)
                    {
                        sb.Append(",");
                        sb.Append(contactList[i].Name);
                    }
                
                    return sb.ToString();
                }
                

                您还可以将第二个 Append 包装在“if”中,以测试 Name 属性是否包含双引号或逗号,如果是,则适当地对其进行转义。

                【讨论】:

                  【解决方案9】:

                  您的代码并不真正符合full CSV format。如果您只是从没有逗号、前导/尾随空格、制表符、换行符或引号的数据生成 CSV,那应该没问题。但是,在大多数现实世界的数据交换场景中,您确实需要完整的实现。

                  为了生成正确的 CSV,你可以使用这个:

                  public static String EncodeCsvLine(params String[] fields)
                  {
                      StringBuilder line = new StringBuilder();
                  
                      for (int i = 0; i < fields.Length; i++)
                      {
                          if (i > 0)
                          {
                              line.Append(DelimiterChar);
                          }
                  
                          String csvField = EncodeCsvField(fields[i]);
                          line.Append(csvField);
                      }
                  
                      return line.ToString();
                  }
                  
                  static String EncodeCsvField(String field)
                  {
                      StringBuilder sb = new StringBuilder();
                      sb.Append(field);
                  
                      // Some fields with special characters must be embedded in double quotes
                      bool embedInQuotes = false;
                  
                      // Embed in quotes to preserve leading/tralining whitespace
                      if (sb.Length > 0 && 
                          (sb[0] == ' ' || 
                           sb[0] == '\t' ||
                           sb[sb.Length-1] == ' ' || 
                           sb[sb.Length-1] == '\t' ))
                      {
                          embedInQuotes = true;
                      }
                  
                      for (int i = 0; i < sb.Length; i++)
                      {
                          // Embed in quotes to preserve: commas, line-breaks etc.
                          if (sb[i] == DelimiterChar || 
                              sb[i]=='\r' || 
                              sb[i]=='\n' || 
                              sb[i] == '"') 
                          { 
                              embedInQuotes = true;
                              break;
                          }
                      }
                  
                      // If the field itself has quotes, they must each be represented 
                      // by a pair of consecutive quotes.
                      sb.Replace("\"", "\"\"");
                  
                      String rv = sb.ToString();
                  
                      if (embedInQuotes)
                      {
                          rv = "\"" + rv + "\"";
                      }
                  
                      return rv;
                  }
                  

                  可能不是世界上最高效的代码,但它已经过测试。与快速示例代码相比,现实世界很糟糕:)

                  【讨论】:

                  • 正如在另一个答案中发布的那样,有一些库可以做到这一点(例如:OpenCSV),它们实际上也有测试框架/单元测试。
                  • 这两个子程序终于解决了我一直在追的问题。没错,比简单地用逗号将所有数据舔并粘在一起要长一点,但处理我的 400,000 行导出时没有问题。
                  【解决方案10】:

                  我以前用过这种方法。 StringBuilder 的 Length 属性不是只读的,因此将其减去一个意味着截断最后一个字符。但是你必须确保你的长度不为零(如果你的列表为空,就会发生这种情况),因为将长度设置为小于零是错误的。

                  public string ReturnAsCSV(ContactList contactList)
                  {
                      StringBuilder sb = new StringBuilder();
                  
                      foreach (Contact c in contactList)       
                      { 
                          sb.Append(c.Name + ",");       
                      }
                  
                      if (sb.Length > 0)  
                          sb.Length -= 1;
                  
                      return sb.ToString();  
                  }
                  

                  【讨论】:

                    【解决方案11】:

                    为什么不使用其中一个开源 CSV 库?

                    我知道对于看起来如此简单的事情来说这听起来有点矫枉过正,但正如您可以通过 cmets 和代码 sn-ps 看出的那样,这不仅仅是表面上看起来那么简单。除了处理完整的 CSV 合规性之外,您最终还需要处理 CSV 的读取和写入...并且您可能需要文件操作。

                    我之前在我的一个项目中使用过Open CSV(但还有很多其他项目可供选择)。这当然让我的生活更轻松。 ;)

                    【讨论】:

                      【解决方案12】:

                      我为此写了一个小类,以防其他人发现它有用...

                      public class clsCSVBuilder
                      {
                          protected int _CurrentIndex = -1;
                          protected List<string> _Headers = new List<string>();
                          protected List<List<string>> _Records = new List<List<string>>();
                          protected const string SEPERATOR = ",";
                      
                          public clsCSVBuilder() { }
                      
                          public void CreateRow()
                          {
                              _Records.Add(new List<string>());
                              _CurrentIndex++;
                          }
                      
                          protected string _EscapeString(string str)
                          {
                              return string.Format("\"{0}\"", str.Replace("\"", "\"\"")
                                                                  .Replace("\r\n", " ")
                                                                  .Replace("\n", " ")
                                                                  .Replace("\r", " "));
                          }
                      
                          protected void _AddRawString(string item)
                          {
                              _Records[_CurrentIndex].Add(item);
                          }
                      
                          public void AddHeader(string name)
                          {
                              _Headers.Add(_EscapeString(name));
                          }
                      
                          public void AddRowItem(string item)
                          {
                              _AddRawString(_EscapeString(item));
                          }
                      
                          public void AddRowItem(int item)
                          {
                              _AddRawString(item.ToString());
                          }
                      
                          public void AddRowItem(double item)
                          {
                              _AddRawString(item.ToString());
                          }
                      
                          public void AddRowItem(DateTime date)
                          {
                              AddRowItem(date.ToShortDateString());
                          }
                      
                          public static string GenerateTempCSVPath()
                          {
                              return Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString().ToLower().Replace("-", "") + ".csv");
                          }
                      
                          protected string _GenerateCSV()
                          {
                              StringBuilder sb = new StringBuilder();
                      
                              if (_Headers.Count > 0)
                              {
                                  sb.AppendLine(string.Join(SEPERATOR, _Headers.ToArray()));
                              }
                      
                              foreach (List<string> row in _Records)
                              {
                                  sb.AppendLine(string.Join(SEPERATOR, row.ToArray()));
                              }
                      
                              return sb.ToString();
                          }
                      
                          public void SaveAs(string path)
                          {
                              using (StreamWriter sw = new StreamWriter(path))
                              {
                                  sw.Write(_GenerateCSV());
                              }
                          }
                      }
                      

                      【讨论】:

                        【解决方案13】:

                        我使用CSVHelper - 这是一个很棒的开源库,可让您一次生成一个兼容的 CSV 流或自定义映射您的类:

                        public string ReturnAsCSV(ContactList contactList)
                        {
                            StringBuilder sb = new StringBuilder();
                            using (StringWriter stringWriter = new StringWriter(sb))
                            {
                                using (var csvWriter = new CsvHelper.CsvWriter(stringWriter))
                                {
                                    csvWriter.Configuration.HasHeaderRecord = false;
                                    foreach (Contact c in contactList)
                                    {
                                        csvWriter.WriteField(c.Name);
                                    }
                                }
                            }
                            return sb.ToString();
                        }
                        

                        或者如果你映射然后是这样的:csvWriter.WriteRecords&lt;ContactList&gt;(contactList);

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 2017-12-12
                          • 2023-03-24
                          • 2012-10-03
                          • 2016-03-07
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多