دمج قائمة سلاسل نصية إلى CSV في C#: تحليل داخلي

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا دمج السلاسل النصية ليس مجرد "جمع"

\n\n

تخيل أنك تكتب خدمة ويب بلغة C# تستقبل قائمة من العلامات (tags) من المستخدم وتحتاج إلى إرسالها إلى قاعدة البيانات كسلسلة نصية واحدة مفصولة بفواصل. أو ربما تقوم بإنشاء تقرير CSV للتصدير. أو تسجل مجموعة من المعرفات. موقف مألوف، أليس كذلك؟ سؤال "إنشاء قائمة مفصولة بفواصل من IList أو IEnumerable" على Stack Overflow حصد آلاف المشاهدات - وليس من دون سبب.

\n\n

قد يبدو الأمر بسيطًا: خذ قائمة السلاسل، مرر عليها بحلقة، وأضف الفواصل. لكن في الواقع، تخفي هذه المهمة عدة مطبات: من فواصل زائدة في النهاية إلى انهيار كارثي في الأداء مع البيانات الكبيرة. في هذا المقال، لن نعرض الحلول الجاهزة فحسب، بل سننظر داخل كل منها. ستتعلم كيف تعمل string.Join وStringBuilder وحتى LINQ Aggregate، وستتمكن من اختيار الأداة المناسبة لمهمتك.

\n\n

هيا بنا!

\n\n

المشكلة: طلب بسيط، تعقيدات غير واضحة

\n\n

لنفترض أن لدينا مجموعة من السلاسل النصية:

\n\n
IEnumerable<string> fruits = new List<string> { "Apple", "Banana", "Cherry", "Date" };
\n\n

نريد الحصول على: "Apple,Banana,Cherry,Date". أول ما يتبادر إلى ذهن المبتدئ هو كتابة حلقة:

\n\n
string result = "";\nbool first = true;\nforeach (var fruit in fruits)\n{\n    if (!first)\n        result += ",";\n    result += fruit;\n    first = false;\n}
\n\n

هذا الكود يعمل، لكنه يفعل ذلك بطريقة سيئة. لماذا؟ لأن السلاسل النصية في C# غير قابلة للتغيير. كل عملية += تنشئ سلسلة جديدة في الذاكرة، وتنسخ محتويات السابقة إليها. مع 4 عناصر، هذا غير ملحوظ، لكن مع 10,000 سلسلة، ستحصل على تعقيد تربيعي O(n²) واستهلاك هائل للذاكرة. داخليًا، هذا يعني آلاف التخصيصات وعمليات جمع القمامة.

\n\n

من الواضح أننا بحاجة إلى نهج أكثر ذكاءً. وهنا تظهر الشخصيات الرئيسية على المسرح.

\n\n

الطريقة 1: string.Join - ملك الأداء

\n\n

الطريقة الأصح والأكثر كفاءة هي استخدام الأسلوب الثابت string.Join:

\n\n
string csv = string.Join(",", fruits);\nConsole.WriteLine(csv); // Apple,Banana,Cherry,Date
\n\n

سطر واحد من الكود وينتهي الأمر. دعنا ننظر داخل string.Join (في .NET Core / .NET 5+).

\n\n

ماذا يحدث داخليًا؟

\n
    \n
  • حساب الطول: يقوم الأسلوب أولاً بالمرور على المجموعة بأكملها (إذا كانت IEnumerable<T>) لمعرفة العدد الإجمالي للأحرف. إذا كانت المجموعة مصفوفة أو IList، فإنه يحصل على الطول فورًا عبر Count.
  • \n
  • تخصيص المخزن المؤقت: يتم تخصيص مصفوفة أحرف كبيرة واحدة (char[]) بالحجم المطلوب: مجموع أطوال جميع السلاسل + (عدد السلاسل - 1) * طول الفاصل.
  • \n
  • النسخ: يتم نسخ جميع السلاسل والفواصل إلى هذا المخزن المؤقت دون أي تخصيص إضافي.
  • \n
  • إنشاء السلسلة: من المخزن المؤقت، يتم إنشاء سلسلة نصية نهائية واحدة.
  • \n
\n\n

النتيجة: تخصيص ذاكرة واحد (للسلسلة النهائية) + تخصيص المخزن المؤقت المؤقت. لا توجد سلاسل وسيطة. التعقيد هو O(n) تمامًا.

\n\n

بالنسبة لـ IList<string>، يعمل الأسلوب بشكل أسرع لأنه يمكنه الحصول فورًا على Count وطول كل عنصر عبر المفهرس، دون استدعاء GetEnumerator.

\n\n

الطريقة 2: StringBuilder - الصديق القديم

\n\n

قبل ظهور string.Join المحسّن (أو إذا كنت عالقًا في إصدار قديم من .NET Framework) كان الحل القياسي هو StringBuilder:

\n\n
var sb = new StringBuilder();\nbool first = true;\nforeach (var fruit in fruits)\n{\n    if (!first)\n        sb.Append(',');\n    sb                

المدونات

توصيات الكتب