Frage

Typische Art, eine zu erstellen CSV Zeichenfolge (Pseudocode):

  1. Erstellen Sie ein CSV-Containerobjekt (wie einen StringBuilder in C#).
  2. Durchlaufen Sie die Zeichenfolgen, die Sie hinzufügen möchten, und fügen Sie nach jeder Zeichenfolge ein Komma hinzu.
  3. Entfernen Sie nach der Schleife das letzte überflüssige Komma.

Codebeispiel:

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

Mir gefällt die Idee, das Komma hinzuzufügen, indem überprüft wird, ob der Container leer ist. Aber bedeutet das nicht mehr Verarbeitung, da bei jedem Vorkommen die Länge der Zeichenfolge überprüft werden muss?

Meiner Meinung nach sollte es eine einfachere/sauberere/effizientere Möglichkeit geben, dieses letzte Komma zu entfernen.Irgendwelche Ideen?

War es hilfreich?

Lösung

Du könntest benutzen LINQ to Objects:

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

Natürlich könnte das alles in einer Zeile erledigt werden, aber in zwei Zeilen ist es etwas klarer.

Andere Tipps

Ihr Code ist nicht wirklich kompatibel mit vollständiges CSV-Format.Wenn Sie nur CSV aus Daten generieren, die keine Kommas, führenden/nachgestellten Leerzeichen, Tabulatoren, Zeilenumbrüche oder Anführungszeichen enthalten, sollte es in Ordnung sein.In den meisten realen Datenaustauschszenarien benötigen Sie jedoch die vollständige Implementierung.

Für die Generierung in die richtige CSV-Datei können Sie Folgendes verwenden:

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

Möglicherweise nicht der effizienteste Code der Welt, aber er wurde getestet.Die reale Welt ist scheiße im Vergleich zu schnellem Beispielcode :)

Warum nicht eine der verfügbaren Open-Source-CSV-Bibliotheken verwenden?

Ich weiß, es klingt für etwas, das so einfach erscheint, übertrieben, aber wie Sie an den Kommentaren und Codeschnipseln erkennen können, steckt mehr dahinter, als man auf den ersten Blick sieht.Zusätzlich zur vollständigen CSV-Konformität möchten Sie möglicherweise auch das Lesen und Schreiben von CSVs übernehmen ...und Sie möchten möglicherweise eine Dateimanipulation.

Ich habe verwendet CSV öffnen bei einem meiner Projekte zuvor (aber es gibt noch viele andere zur Auswahl).Es hat mein Leben auf jeden Fall einfacher gemacht.;)

Vergessen Sie nicht unseren alten Freund „für“.Es sieht nicht so gut aus wie foreach, hat aber den Vorteil, dass man mit dem zweiten Element beginnen kann.

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

Sie können das zweite Anhängen auch in ein „if“ einschließen, das testet, ob die Name-Eigenschaft ein doppeltes Anführungszeichen oder ein Komma enthält, und wenn ja, diese entsprechend maskieren.

Sie könnten stattdessen das Komma als erstes in Ihrem foreach einfügen.

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

Sie könnten auch eine Reihe davon erstellen c.Name Daten und Nutzung String.Join Methode zum Erstellen Ihrer Linie.

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

Dies ist möglicherweise nicht so leistungsstark wie das StringBuilder Ansatz, aber es sieht definitiv sauberer aus.

Vielleicht möchten Sie auch die Verwendung in Betracht ziehen .CurrentCulture.TextInfo.ListSeparator anstelle eines fest codierten Kommas – Wenn Ihre Ausgabe in andere Anwendungen importiert werden soll, könnten Probleme damit auftreten.ListSeparator kann in verschiedenen Kulturen unterschiedlich sein, und zumindest MS Excel berücksichtigt diese Einstellung.Also:

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

Mir gefällt die Idee, das Komma hinzuzufügen, indem überprüft wird, ob der Container leer ist. Aber bedeutet das nicht mehr Verarbeitung, da bei jedem Vorkommen die Länge der Zeichenfolge überprüft werden muss?

Wenn Sie zu früh optimieren, wäre der Leistungseinbruch vernachlässigbar.

Nur eine Überlegung, aber denken Sie daran, Kommas und Anführungszeichen (") in den Feldwerten zu verwenden, da Ihre CSV-Datei andernfalls den Verbraucherleser beschädigen könnte.

Ich habe dazu eine kleine Klasse geschrieben, für den Fall, dass es jemand anderes nützlich findet ...

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

Ich habe diese Methode schon einmal verwendet.Die Eigenschaft „Length“ von StringBuilder ist NICHT schreibgeschützt. Wenn Sie sie also um eins subtrahieren, wird das letzte Zeichen abgeschnitten.Sie müssen jedoch sicherstellen, dass Ihre Länge zunächst nicht Null ist (was passieren würde, wenn Ihre Liste leer ist), da es ein Fehler ist, die Länge auf weniger als Null festzulegen.

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

ich benutze CSVHelper – Es handelt sich um eine großartige Open-Source-Bibliothek, mit der Sie konforme CSV-Streams Element für Element generieren oder Ihre Klassen individuell zuordnen können:

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

oder wenn Sie eine Karte erstellen, dann etwa so: csvWriter.WriteRecords<ContactList>(contactList);

Wie wäre es mit etwas Trimmen?

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

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

    return sb.ToString().Trim(',');
}

Wie wäre es, wenn Sie nachverfolgen, ob Sie sich beim ersten Element befinden, und nur ein Komma hinzufügen? Vor das Element, wenn es nicht das erste ist.

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();
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top