题
创建一个的典型方式 CSV 字符串(伪代码):
- 创建一个 CSV 容器对象(如 C# 中的 StringBuilder)。
- 循环遍历要添加的字符串,并在每个字符串后添加一个逗号。
- 循环之后,删除最后一个多余的逗号。
代码示例:
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 到对象:
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...并且您可能需要文件操作。
我用过 打开 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.姓名 数据和使用 字符串连接 创建线路的方法。
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());
}
这可能不如 字符串生成器 方法,但看起来确实更干净。
另外,您可能需要考虑使用 .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<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());
}
}
}
我以前用过这个方法。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();
}
我用 CSV助手 - 它是一个很棒的开源库,可让您一次生成一个元素的兼容 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>(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();
}