Les meilleures pratiques pour les objets sérialisation à un format de chaîne personnalisée pour une utilisation dans un fichier de sortie

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

Question

J'étais sur le point de mettre en œuvre une substitution de ToString () sur une classe d'affaires particulier afin de produire un format Excel convivial pour écrire dans un fichier de sortie, qui sera repris plus tard et traité. Voici ce que les données est censé ressembler à:

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

Il est pas une grosse affaire pour moi de faire juste une chaîne de format et passer outre ToString(), mais cela va changer le comportement des ToString() pour tous les objets que je décide de sérialisation de cette façon, ce qui rend la mise en œuvre de ToString() tous loques dans la bibliothèque.

Maintenant, je l'ai lu sur IFormatProvider et une classe implémentant cela ressemble à une bonne idée, mais je suis encore un peu confus au sujet où tout cette logique doit résider et comment construire la classe formatter.

Qu'est-ce que vous les gars quand vous avez besoin de faire un fichier CSV, délimité par des tabulations ou une autre chaîne arbitraire non XML à partir d'un objet?

Était-ce utile?

La solution

Voici une manière générique pour la création de CSV à partir d'une liste d'objets, en utilisant la réflexion:

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

De nombreuses variantes peuvent être apportées, telles que l'écriture directement dans un fichier ToCSV (), ou le remplacement de la StringBuilder avec un rendement et des déclarations IEnumerable.

Autres conseils

Voici une version simplifiée de l'idée de CSV par Hejndorf (sans la surcharge de la mémoire car elle donne chaque ligne à son tour). En raison de la demande populaire, il prend également en charge les champs et les propriétés simples par l'utilisation de Concat.

Mise à jour 18 mai 2017

Cet exemple n'a jamais été destiné à être une solution complète, juste faire avancer l'idée originale affichée par Per Hejndorf. Pour générer CSV valide, vous devez remplacer les caractères délimiteurs texte, dans le texte, avec une séquence de 2 caractères délimiteurs. par exemple. simple .Replace("\"", "\"\"").

Mise à jour le 12 février 2016

Après avoir utilisé mon propre code à nouveau dans un projet aujourd'hui, je réalise que je ne devrais pas avoir pris quoi que ce soit pour acquis quand j'ai commencé par l'exemple de @Per Hejndorf. Il est plus logique de prendre un délimiteur par défaut « » (virgule) et faire le delimiter le second, option , paramètre. Ma propre version de la bibliothèque fournit également un paramètre 3e header qui contrôle si une ligne d'en-tête doit être retourné comme parfois vous ne souhaitez que les données.

par exemple.

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

si vous utilisez alors comme ceci pour délimité par des virgules:

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

ou comme cela pour un autre délimiteur (par exemple TAB):

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

Exemples pratiques

Liste d'écriture dans un fichier CSV délimité par des virgules

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

ou écrire des tabulations

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

Si vous avez des champs / propriétés complexes, vous aurez besoin de les filtrer des clauses de sélection.


Les versions précédentes et détails ci-dessous:

Voici une version simplifiée de l'idée de CSV par Hejndorf (sans la surcharge de la mémoire car elle donne chaque ligne à son tour) et a seulement 4 lignes de code:)

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

Vous pouvez itérer comme ceci:

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

objects est une liste fortement typée d'objets.

Cette variation comprend les champs publics et simples propriétés publiques:

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

En règle générale, je ne jure que remplaçant toString comme outil de débogage, si elle est pour la logique métier, il devrait être une méthode explicite de la classe / interface.

Pour sérialisation simple comme cela, je vous suggère d'avoir une classe séparée qui connaît votre bibliothèque de sortie CSV et vos objets métier qui fait la sérialisation plutôt que de pousser la sérialisation dans l'entreprise eux-mêmes des objets.

De cette façon, vous vous retrouvez avec une classe par format de sortie qui produit une vue de votre modèle.

Pour sérialisation plus complexe où vous essayez d'écrire un objet graphique pour la persistance je considère le mettre dans les classes d'affaires -. Mais seulement si elle fait pour un code plus propre

Le problème avec les solutions que j'ai trouvé à ce jour est qu'ils ne vous permettent pas d'exporter un sous-ensemble de propriétés, mais seulement l'objet entier. La plupart du temps, quand nous avons besoin d'exporter des données au format CSV, nous devons « adapter » son format d'une manière précise, donc je créé cette méthode d'extension simple qui me permet de le faire en passant un tableau de paramètres de type Func<T, string> à spécifier le mappage.

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

Utilisation:

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

Génère:

Hayek,Friedrich
Rothbard,Murray
Brent,David

J'ai eu un problème la variation de HiTech magie étaient deux propriétés avec la même valeur, que l'on pourrait obtenir peuplé. Cela semble avoir fixé:

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

Autant en emporte la réponse de codage a été très utile. J'ai fait quelques changements dans le but de gérer gremlins texte qui FLEXIBLE la sortie.

 /******************************************************/
    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;
       }
    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top