Best Practices für die Objekte zu einem benutzerdefinierten Format-String für die Verwendung in einer Ausgabedatei Serialisierung

StackOverflow https://stackoverflow.com/questions/1179816

Frage

Ich war gerade dabei, eine Überschreibung von ToString () auf einer bestimmten Business-Klasse zu implementieren, um ein Excel-freundliches Format zu erzeugen, um eine Ausgabedatei zu schreiben, die später abgeholt werden und verarbeitet werden. Hier ist, was die Daten aussehen soll wie:

5555555 "LASTN SR, FIRSTN"  5555555555  13956 STREET RD     TOWNSVILLE  MI  48890   25.88   01-003-06-0934

Es ist keine große Sache für mich nur ein Format-String zu machen und ToString() außer Kraft setzen, aber das wird das Verhalten von ToString() für alle Objekte ändern, die ich entscheiden, diesen Weg zu serialisiert, so dass die Umsetzung der ToString() ganze Bibliothek zackig.

Nun, ich habe gelesen, bis auf IFormatProvider es klingt wie eine gute Idee, und eine Klasse der Umsetzung, aber ich bin immer noch ein wenig verwirrt darüber, wo all diese Logik befinden soll und wie die Formatter-Klasse zu bauen.

Was tun Sie Jungs tun, wenn Sie eine CSV vornehmen müssen, durch Tabulatoren getrennte oder eine andere nicht-XML beliebige Zeichenfolge aus einem Objekt?

War es hilfreich?

Lösung

Hier ist eine generische Art und Weise für die aus einer Liste von Objekten CSV erstellen, die der Reflexion:

    public static string ToCsv<T>(string separator, IEnumerable<T> objectlist)
    {
        Type t = typeof(T);
        FieldInfo[] fields = t.GetFields();

        string header = String.Join(separator, fields.Select(f => f.Name).ToArray());

        StringBuilder csvdata = new StringBuilder();
        csvdata.AppendLine(header);

        foreach (var o in objectlist) 
            csvdata.AppendLine(ToCsvFields(separator, fields, o));

        return csvdata.ToString();
    }

    public static string ToCsvFields(string separator, FieldInfo[] fields, object o)
    {
        StringBuilder linie = new StringBuilder();

        foreach (var f in fields)
        {
            if (linie.Length > 0)
                linie.Append(separator);

            var x = f.GetValue(o);

            if (x != null)
                linie.Append(x.ToString());
        }

        return linie.ToString();
    }

Viele Variationen gemacht werden können, wie zum Beispiel das Schreiben in ToCsv in eine Datei direkt aus (), oder Ersetzen der String mit einem IEnumerable und Ertragsrechnung.

Andere Tipps

Dies ist eine vereinfachte Version von Per Hejndorf der CSV-Idee (ohne den Speicher-Overhead, da sie jede Zeile wiederum ergibt). Aufgrund der großen Nachfrage es unterstützt auch beide Felder und einfache Eigenschaften durch die Verwendung von Concat.

Aktualisieren 18. Mai 2017

Dieses Beispiel wurde nie eine komplette Lösung sein soll, nur die ursprüngliche Idee von Per Hejndorf geschrieben voran. So generieren gültige CSV müssen Sie alle Textbegrenzungszeichen ersetzen, innerhalb des Textes, mit einer Folge von zwei Begrenzungszeichen. z.B. ein einfaches .Replace("\"", "\"\"").

Aktualisieren 12. Februar 2016

meinen eigenen Code wieder in einem Projekt heute Nach der Verwendung, erkennen ich, ich soll nichts für selbstverständlich gehalten habe, wenn ich aus dem Beispiel von @Per Hejndorf gestartet. Es macht mehr Sinn, ein Standard-Trennzeichen „“ (Komma) zu übernehmen und das Trennzeichen die zweiten machen, optional , Parameter. Meine eigene Bibliothek Version bietet auch einen dritten header Parameter, der eine Kopfzeile zurückgegeben kontrolliert, ob werden soll, wie manchmal nur die Daten will.

z.

public static IEnumerable<string> ToCsv<T>(IEnumerable<T> objectlist, string separator = ",", bool header = true)
{
    FieldInfo[] fields = typeof(T).GetFields();
    PropertyInfo[] properties = typeof(T).GetProperties();
    if (header)
    {
        yield return String.Join(separator, fields.Select(f => f.Name).Concat(properties.Select(p=>p.Name)).ToArray());
    }
    foreach (var o in objectlist)
    {
        yield return string.Join(separator, fields.Select(f=>(f.GetValue(o) ?? "").ToString())
            .Concat(properties.Select(p=>(p.GetValue(o,null) ?? "").ToString())).ToArray());
    }
}

, damit Sie es dann wie folgt verwenden für kommagetrennte:

foreach (var line in ToCsv(objects))
{
    Console.WriteLine(line);
}

oder, wie dies für ein anderes Trennzeichen (z TAB):

foreach (var line in ToCsv(objects, "\t"))
{
    Console.WriteLine(line);
}

Praktische Beispiele

Schreibliste in eine kommagetrennte CSV-Datei

using (TextWriter tw = File.CreateText("C:\testoutput.csv"))
{
    foreach (var line in ToCsv(objects))
    {
        tw.WriteLine(line);
    }
}

oder schreiben Sie es Tab-separierte

using (TextWriter tw = File.CreateText("C:\testoutput.txt"))
{
    foreach (var line in ToCsv(objects, "\t"))
    {
        tw.WriteLine(line);
    }
}

Wenn Sie komplexe Felder / Eigenschaften haben, müssen Sie sie aus der Auswahl Klauseln filtern.


Frühere Versionen und Details unter:

Dies ist eine vereinfachte Version von Per Hejndorf des CSV Idee und hat nur 4 Zeilen Code (ohne den Speicher-Overhead, da sie jede Zeile wiederum ergeben):)

public static IEnumerable<string> ToCsv<T>(string separator, IEnumerable<T> objectlist)
{
    FieldInfo[] fields = typeof(T).GetFields();
    yield return String.Join(separator, fields.Select(f => f.Name).ToArray());
    foreach (var o in objectlist)
    {
        yield return string.Join(separator, fields.Select(f=>(f.GetValue(o) ?? "").ToString()).ToArray());
    }
}

Sie können es wie folgt durchlaufen:

foreach (var line in ToCsv(",", objects))
{
    Console.WriteLine(line);
}

wo objects ist eine stark typisierte Liste von Objekten.

Diese Variante umfasst sowohl öffentliche Felder und einfache öffentliche Eigenschaften:

public static IEnumerable<string> ToCsv<T>(string separator, IEnumerable<T> objectlist)
{
    FieldInfo[] fields = typeof(T).GetFields();
    PropertyInfo[] properties = typeof(T).GetProperties();
    yield return String.Join(separator, fields.Select(f => f.Name).Concat(properties.Select(p=>p.Name)).ToArray());
    foreach (var o in objectlist)
    {
        yield return string.Join(separator, fields.Select(f=>(f.GetValue(o) ?? "").ToString())
            .Concat(properties.Select(p=>(p.GetValue(o,null) ?? "").ToString())).ToArray());
    }
}

Als Faustregel gilt: I nur toString als Werkzeug für die Fehlersuche übergeordneten befürworten, wenn es für die Business-Logik ist es sollte eine explizite Methode für die Klasse / Schnittstelle sein.

Für einfache Serialisierung wie dieses Ich würde vorschlagen, eine eigene Klasse hat, die über Ihre CSV-Ausgabebibliothek und Ihre Business-Objekte kennt, die die Serialisierung tut, anstatt Drücken der Serialisierung in die Geschäftsobjekte selbst.

Auf diese Weise kann mit einer Klasse pro Ausgabeformat am Ende, die eine Ansicht des Modells erzeugt.

Für komplexere Serialisierung, wo Sie versuchen, ein Objekt Diagramm zu schreiben, für die Persistenz halte ich würde es in den Business-Klassen setzen -. Aber nur, wenn es für eine sauberere Code macht

Das Problem mit den Lösungen, die ich bisher gefunden ist, dass sie nicht zulassen, eine Teilmenge der Eigenschaften exportieren, sondern nur das gesamte Objekt. Die meiste Zeit, wenn wir den Export von Daten in CSV benötigen, müssen wir „maßzuschneidern“ sein Format in einer präzisen Art und Weise, so habe ich diese einfache Erweiterung Methode, die mir, das zu tun erlaubt durch eine Reihe von Parametern des Typs Func<T, string> vorbei zu geben Sie die Zuordnung.

public static string ToCsv<T>(this IEnumerable<T> list, params Func<T, string>[] properties)
{
    var columns = properties.Select(func => list.Select(func).ToList()).ToList();

    var stringBuilder = new StringBuilder();

    var rowsCount = columns.First().Count;

    for (var i = 0; i < rowsCount; i++)
    {
        var rowCells = columns.Select(column => column[i]);

        stringBuilder.AppendLine(string.Join(",", rowCells));
    }

    return stringBuilder.ToString();
}

Verwendung:

philosophers.ToCsv(x => x.LastName, x => x.FirstName)

Erzeugt:

Hayek,Friedrich
Rothbard,Murray
Brent,David

Ich hatte ein Problem des HiTech Magic Variation zwei Eigenschaften mit dem gleichen Wert waren, nur würde man bevölkert bekommen. Dies scheint es behoben zu haben:

        public static IEnumerable<string> ToCsv<T>(string separator, IEnumerable<T> objectlist)
    {
        FieldInfo[] fields = typeof(T).GetFields();
        PropertyInfo[] properties = typeof(T).GetProperties();
        yield return String.Join(separator, fields.Select(f => f.Name).Union(properties.Select(p => p.Name)).ToArray());
        foreach (var o in objectlist)
        {
            yield return string.Join(separator, (properties.Select(p => (p.GetValue(o, null) ?? "").ToString())).ToArray());
        }
    }

gegangenes Coding Antwort war sehr hilfreich. Ich habe einige Änderungen daran, um Text Gremlins zu handhaben, der den Ausgang Schlauch würde.

 /******************************************************/
    public static IEnumerable<string> ToCsv<T>(IEnumerable<T> objectlist, string separator = ",", bool header = true)
    {
       FieldInfo[] fields = typeof(T).GetFields();
       PropertyInfo[] properties = typeof(T).GetProperties();
       string str1;
       string str2;

       if(header)
       {
          str1 = String.Join(separator, fields.Select(f => f.Name).Concat(properties.Select(p => p.Name)).ToArray());
          str1 = str1 + Environment.NewLine;
          yield return str1;
       }
       foreach(var o in objectlist)
       {
          //regex is to remove any misplaced returns or tabs that would
          //really mess up a csv conversion.
          str2 = string.Join(separator, fields.Select(f => (Regex.Replace(Convert.ToString(f.GetValue(o)), @"\t|\n|\r", "") ?? "").Trim())
             .Concat(properties.Select(p => (Regex.Replace(Convert.ToString(p.GetValue(o, null)), @"\t|\n|\r", "") ?? "").Trim())).ToArray());

          str2 = str2 + Environment.NewLine;
          yield return str2;
       }
    }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top