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

明らかに、それはすべて 1 行で実行できますが、2 行の方が少し明確です。

于 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 ほど見栄えはよくありませんが、2 番目の要素から開始できるという利点があります。

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

Name プロパティに二重引用符またはコンマが含まれているかどうかをテストする "if" で 2 番目の Append をラップし、含まれている場合は適切にエスケープすることもできます。

于 2008-08-07T12:00:51.553 に答える
5

そこにあるオープンソースの CSV ライブラリの 1 つを使用してみませんか?

非常に単純に見えるものに対してやり過ぎのように聞こえることは承知していますが、コメントとコード スニペットからわかるように、目に見える以上のものがあります。完全な CSV 準拠の処理に加えて、最終的には CSV の読み取りと書き込みの両方を処理する必要があり、ファイル操作が必要になる場合があります。

以前、自分のプロジェクトの 1 つでOpen CSVを使用したことがあります (ただし、他にもたくさんの選択肢があります)。それは確かに私の人生を楽にしてくれました。;)

于 2008-08-20T02:14:42.013 に答える
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 に答える
3

代わりに、カンマを foreach 内の最初のものとして追加できます。

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

于 2008-08-07T05:54:00.157 に答える
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 に答える
1

考えただけですが、フィールド値でコンマと引用符 (") を処理することを忘れないでください。そうしないと、CSV ファイルがコンシューマー リーダーを壊す可能性があります。

于 2008-08-07T11:18:09.143 に答える
1

コンテナーが空かどうかを確認してコンマを追加するというアイデアは気に入っていますが、発生するたびに文字列の長さを確認する必要があるため、処理が増えるということではありませんか?

時期尚早に最適化しています。パフォーマンスへの影響はごくわずかです。

于 2008-08-07T06:25:57.503 に答える
1

私は以前にこの方法を使用したことがあります。StringBuilder の Length プロパティは読み取り専用ではないため、1 を引くと最後の文字が切り捨てられます。ただし、長さをゼロ未満に設定するとエラーになるため、最初に長さがゼロでないことを確認する必要があります (これはリストが空の場合に発生します)。

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 に答える
0

私はCSVHelperを使用しています。これは、準拠した CSV ストリームを一度に 1 要素ずつ生成したり、クラスをカスタム マップしたりできる優れたオープンソース ライブラリです。

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 に答える
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

最初のアイテムにいるかどうかを追跡し、最初のアイテムでない場合はアイテムの前にのみカンマを追加してください。

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 に答える