أفضل الممارسات للكائنات التسلسلية بتنسيق سلسلة مخصص للاستخدام في ملف الإخراج

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

سؤال

كنت على وشك تنفيذ تجاوز Tostring () على فئة عمل معينة من أجل إنتاج تنسيق سهل الاستخدام للكتابة إلى ملف إخراج، والذي سيتم التقاطه لاحقا ومعالجته. إليك ما يفترض أن تبدو البيانات:

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

انها ليست مشكلة كبيرة بالنسبة لي فقط لجعل سلسلة تنسيق وتجاوز ToString(), ، ولكن هذا سيغير سلوك ToString() لأي كائنات قررت تسلسل هذه الطريقة، مما يجعل تنفيذ ToString() كل خشنة عبر المكتبة.

الآن، لقد كنت أقرأ iformatprovider., ، وتنفيذ الفصل يبدو وكأنه فكرة جيدة، لكنني ما زلت مرتبكا قليلا حول المكان الذي يجب أن يقيم فيه كل هذا المنطق وكيفية بناء فئة الشكل.

ماذا تفعل يا رفاق عندما تحتاج إلى إنشاء CSV أو علامة التبويب محددة أو بعض السلسلة التعسفية غير XML غير XML من كائن؟

هل كانت مفيدة؟

المحلول

إليك أزياء عامة لإنشاء CSV من قائمة الكائنات، باستخدام التفكير:

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

يمكن إجراء العديد من الاختلافات، مثل الكتابة مباشرة إلى ملف في TOCSV ()، أو استبدال StringBuilder ببيانات iEnumperable والعائد.

نصائح أخرى

فيما يلي نسخة مبسطة من فكرة CSV في Hejndorf (بدون علبة الذاكرة التي تسفر عن كل سطر بدورها). بسبب الطلب الشعبي، يدعم أيضا كل من الحقول والخصائص البسيطة عن طريق استخدام Concat.

تحديث 18 مايو 2017

لم يكن من المفترض أن يكون هذا المثال أبدا حلا كاملا، فما عليك سوى النهوض بالفيد الأصلي المنشور من قبل لكل HEJNDORF. لتوليد CSV صالح، تحتاج إلى استبدال أي أحرف محدد نصية، داخل النص، مع تسلسل حرف محدد 2. على سبيل المثال بسيط .Replace("\"", "\"\"").

تحديث 12 فبراير 2016

بعد استخدام الرمز الخاص بي مرة أخرى في مشروع اليوم، أدركت أنني لا يجب أن أتناول أي شيء مفروحا عندما بدأت من مثال @Per Hejndorf. وبعد من المنطقي أن تفترض حدودا افتراضيا من "،" (فاصلة) وجعل المحدد الثاني، اختياري, ، معامل. توفر إصدار مكتبة الخاص بي أيضا 3RD header المعلمة التي تتحكم في ما إذا كان يجب إرجاع صف رأس كما أحيانا تريد البيانات فقط.

على سبيل المثال

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

لذلك يمكنك استخدامها مثل هذا ل Comma محدد:

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

أو مثل هذا لعدد محدد آخر (مثل علامة التبويب):

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

أمثلة عملية

الكتابة قائمة إلى ملف CSV محدد بفواصل

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

أو اكتب TAB-Delimited

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

إذا كان لديك حقول / عقارات معقدة، فستحتاج إلى تصفيةها من بنود المحددة.


الإصدارات السابقة والتفاصيل أدناه:

إليك نسخة مبسطة من فكرة CSV في HEJNDORF (بدون علبة الذاكرة لأنها تقوم بتوجيه كل سطر بدوره) ولها 4 خطوط فقط من التعليمات البرمجية :)

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

يمكنك التكرار مثل هذا:

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

أين objects هي قائمة مكتوبة بشدة من الكائنات.

يتضمن هذا الاختلاف الحقول العامة والخصائص العامة البسيطة:

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

نظرا لأن قاعدة الإبهام أقوم بالدعوة فقط إلى Tostring كأداة لتصحيح الأخطاء، إذا كان منطق الأعمال، يجب أن تكون طريقة صريحة في الفصل / الواجهة.

بالنسبة إلى تسلسل بسيط مثل هذا، أقترح وجود فئة منفصلة تعرف عن مكتبة إخراج CSV وأجوهات عملك التي تقوم بالتسليط بدلا من دفع التسلسل إلى كائنات العمل نفسها.

بهذه الطريقة، ينتهي بك مع فئة لكل تنسيق إخراج تنتج عرض النموذج الخاص بك.

للحصول على تسلسل أكثر تعقيدا حيث تحاول كتابة رسم بياني كائن للحصول على الثبات، سأفكر في وضعه في فصول الأعمال - ولكن فقط إذا كان يجعل من أجل كود الأنظف.

المشكلة مع الحلول التي وجدتها حتى الآن هي أنها لا تسمح لك بتصدير مجموعة فرعية من الخصائص، ولكن فقط الكائن بأكمله. في معظم الأحيان، عندما نحتاج إلى تصدير البيانات في CSV، نحتاج إلى "خياط" تنسيقها بطريقة دقيقة، لذلك قمت بإنشاء طريقة تمديد هذه البسيطة التي تتيح لي القيام بذلك عن طريق تمرير مجموعة من المعلمات من النوع Func<T, string> لتحديد الخرائط.

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

الاستعمال:

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

يولد:

Hayek,Friedrich
Rothbard,Murray
Brent,David

كان لدي مشكلة في اختلاف Hitech Magic الخاص بخصائص بنفس القيمة، فقط سيتجهد المرء. يبدو أن هذا ثابت:

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

تم إجابة الترميز كانت مفيدة للغاية. لقد قمت ببعض التغييرات الموجهة إليه من أجل التعامل مع مصارحة النصوص التي من شأنها خرطوم الناتج.

 /******************************************************/
    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;
       }
    }
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top