CSV字符串处理

创建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(); } 

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

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

您可以使用LINQ to Objects :

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

显然,这一切都可以在一条线上完成,但两条线路更清晰一些。

您的代码不完全符合完整的CSV格式 。 如果您只是从没有逗号,前导/尾随空格,制表符,换行符或引号的数据生成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; } 

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

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

我知道这听起来有些过于简单,但正如你可以从评论和代码片段中看到的那样,不仅仅是满足于眼睛。 除了处理完整的CSV合规性之外,您最终还是希望同时处理读取和写入CSV …并且您可能需要文件操作。

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

别忘了我们的老朋友“为了”。 它并不像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属性是包含双引号还是逗号,如果是,则适当地转义它们。

您可以将逗号添加为foreach中的第一个内容。

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

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

 public string ReturnAsCSV(ContactList contactList) { List tmpList = new List(); 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()); 

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

您过早地进行了优化,性能损失可以忽略不计。

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

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

 public class clsCSVBuilder { protected int _CurrentIndex = -1; protected List _Headers = new List(); protected List> _Records = new List>(); protected const string SEPERATOR = ","; public clsCSVBuilder() { } public void CreateRow() { _Records.Add(new List()); _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 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()); } } } 

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

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

我使用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(contactList);

一些修剪怎么样?

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

如何跟踪您是否在第一个项目上,并且只该项目之前添加逗号(如果它不是第一个项目)。

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