20

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

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

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

4

13 回答 13

21

您可以使用LINQ to Objects

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

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

于 2008-08-07T05:56:15.957 回答
9

您的代码并不真正符合完整的 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;
}

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

于 2008-08-09T10:47:26.860 回答
5

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

于 2008-08-07T12:00:51.553 回答
5

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

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

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

于 2008-08-20T02:14:42.013 回答
3

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

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

于 2008-08-07T05:54:00.157 回答
3

您还可以创建一个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());
于 2008-08-07T07:37:57.543 回答
1

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

你过早地优化,性能影响可以忽略不计。

于 2008-08-07T06:25:57.503 回答
1

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

于 2008-08-07T11:18:09.143 回答
1

我以前用过这种方法。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();  
}
于 2008-08-20T01:47:02.920 回答
1

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

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());
        }
    }
}
于 2012-06-26T19:34:54.020 回答
0

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

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();
}
于 2008-08-07T05:54:57.813 回答
0

修剪一下怎么样?

public string ReturnAsCSV(ContactList contactList)
{
    StringBuilder sb = new StringBuilder();

    foreach (Contact c in contactList)
    {
        sb.Append(c.Name + ",");
    }

    return sb.ToString().Trim(',');
}
于 2008-08-07T08:19:56.040 回答
0

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

于 2012-06-26T20:06:39.263 回答