سؤال

طريقة نموذجية من خلق CSV سلسلة (pseudocode):

  1. إنشاء ملف CSV كائن حاوية (مثل بـ stringbuilder في C#).
  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();
}

أنا أحب فكرة إضافة فاصلة عن طريق التحقق من إذا كانت الحاوية فارغة, ولكن لا يعني ذلك المزيد من المعالجة كما أنه يحتاج إلى التحقق من طول السلسلة على كل ما يحدث?

أشعر أنه ينبغي أن يكون هناك أسهل/نظافة/طريقة أكثر كفاءة إزالة آخر فاصلة.أي أفكار ؟

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

المحلول

هل يمكن استخدام LINQ إلى الكائنات:

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

من الواضح أنه يمكن أن يكون كل ذلك في سطر واحد, ولكن هذا قليلا أكثر وضوحا على اثنين.

نصائح أخرى

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

من أجل جيل سليم 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;
}

قد لا يكون في العالم الأكثر فعالية الكود ولكن تم اختبارها.العالم الحقيقي سئ مقارنة سريعة نموذج التعليمات البرمجية :)

لماذا لا تستخدم واحدة مفتوحة المصدر CSV المكتبات هناك ؟

وأنا أعلم أنه يبدو وكأنه مبالغة شيء يبدو في غاية البساطة ، ولكن كما يمكن أن أقول لكم من خلال التعليقات و مقتطفات الشفرة هناك أكثر مما تراه العين.بالإضافة إلى التعامل مع كامل CSV الامتثال ، في نهاية المطاف سوف ترغب في التعامل مع كل من القراءة والكتابة ملفات csv...و قد تحتاج ملف التلاعب.

لقد استعملت فتح CSV على واحدة من بلدي المشاريع من قبل (ولكن هناك الكثير من الآخرين للاختيار من بينها).بالتأكيد جعل حياتي أسهل.;)

لا ننسى صديقنا القديم "عن".انها ليست جميلة كما foreach ولكن لديها ميزة كونها قادرة على البدء في العنصر الثاني.

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

يمكنك أيضا التفاف الثانية إلحاق في "لو" أن الاختبارات ما إذا كان اسم العقار يحتوي على ضعف الاقتباس أو فاصلة ، وإذا كان الأمر كذلك, الهروب منها بشكل مناسب.

يمكنك بدلا من ذلك إضافة فاصلة أول شيء داخل foreach.

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

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

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

هذا قد لا يكون performant كما بـ stringbuilder النهج, ولكن يبدو بالتأكيد أكثر نظافة.

أيضا, قد ترغب في النظر في استخدام .CurrentCulture.TextInfo.ListSeparator بدلا من القرص الثابت تلوينها فاصلة-إذا الإخراج الخاص بك هو الذهاب إلى أن المستوردة في تطبيقات أخرى ، قد يكون لديك مشاكل مع ذلك.ListSeparator قد تكون مختلفة عبر الثقافات المختلفة, و MS Excel على الأقل يكرم هذا الإعداد.لذلك:

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

أنا أحب فكرة إضافة فاصلة عن طريق التحقق من إذا كانت الحاوية فارغة, ولكن لا يعني ذلك المزيد من المعالجة كما أنه يحتاج إلى التحقق من طول السلسلة على كل ما يحدث?

كنت قبل الأوان تحسين الأداء ضرب ستكون ضئيلة.

مجرد فكرة, ولكن تذكر أن التعامل مع فاصلة و علامات اقتباس (") في مجال القيم ، وإلا ملف CSV قد كسر المستهلكين القارئ.

كتبت فئة صغيرة في حالة شخص آخر يرى أنه من المفيد...

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

لقد استعملت هذه الطريقة من قبل.طول ممتلكات بـ stringbuilder ليست للقراءة فقط حتى طرح عليه من قبل أحد يعني اقتطاع الحرف الأخير.ولكن لديك للتأكد من طول لا لتبدأ من الصفر (الذي يمكن أن يحدث إذا كانت القائمة فارغة) لأن تحديد المدة إلى أقل من الصفر هو خطأ.

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

يمكنني استخدام CSVHelper - انها كبيرة مفتوحة المصدر المكتبة التي تمكنك من توليد CSV متوافق مع تيارات عنصر واحد في وقت واحد أو العرف-خريطة الفصول الدراسية:

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

كيف حول بعض تقليم ؟

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

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

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

ماذا عن تتبع ما إذا كنت على البند الأول فقط إضافة فاصلة قبل البند إذا لم يكن أول واحد.

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