Menu
flag Ar iconالعربيةdown icon

الكتابة في ملف وقراءة ملف في C#: File وStreamWriter وStreamReader

كيف تكتب في ملف وتقرأ ملفًا في C#: File.WriteAllText وReadAllText وAppendAllText وWriteAllLines وReadAllLines للملفات الكاملة، وStreamWriter وStreamReader للملفات الكبيرة، والمسارات بـ Path.Combine، والمجلدات، والترميزات، والاستثناءات التي يجب أن تعالجها شيفرة الملفات.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

في فضاء الأسماء System.IO كل ما يلزم للعمل مع الملفات. لمعظم المهام يكفي استدعاء واحد على الصنف الساكن File: يفتح الملف، ويقرؤه أو يكتبه، ويغلقه. وللملفات الكبيرة أو التحكم الدقيق استخدم StreamReader وStreamWriter. أضف using System.IO; إلى كل مثال أدناه.

كتابة ملف كامل وقراءته

تنشئ File.WriteAllText ملفًا (أو تكتب فوق ملف موجود) بالنص الذي تعطيها. وتعيد File.ReadAllText الملف كله كنص واحد. وتضيف File.AppendAllText إلى النهاية:

المخرجات:

True
Buy coffee
Call the dentist
Renew passport
43 characters
Start over
False

يُحسم المسار النسبي مثل "notes.txt" مقابل المجلد الحالي للعملية، وهو ليس دائمًا المجلد الذي يعيش فيه برنامجك (قد تبدأ الخدمة أو المهمة المجدولة أو مشغّل الاختبارات في مكان آخر). وللملفات التي تأتي مع تطبيقك ابنِ المسار من AppContext.BaseDirectory.

تكتب WriteAllText فوق الملف دون أن تسأل. إن كان فقدان المحتوى القديم مهمًا فافحص File.Exists أولًا أو اكتب إلى ملف مؤقت ثم انقله إلى مكانه بـ File.Move.

الأسطر: WriteAllLines وReadAllLines وReadLines

ملفات السجلات، سجل في كل سطر، شائعة بما يكفي لأن تكون لها دوالها الخاصة. تكتب WriteAllLines كل نص يليه سطر جديد؛ وتعيد ReadAllLines مصفوفة string[]:

المخرجات:

4 lines, header: date,product,amount
Total: 45.60
2026-03-03,Stapler,7.00

الفرق بين القارئين مهم للحجم. تقرأ ReadAllLines الملف كله في الذاكرة قبل أن تعود. أما ReadLines فتعيد IEnumerable<string> كسولة تقرأ كلما مررت، فيستخدم foreach على سجل من 5 GB بضعة كيلوبايتات. ومع LINQ تتوقف ReadLines(...).Where(...).Take(10) عن القراءة بعد أن تجد عشرة تطابقات.

تحليل CSV بـ Split(',') يعمل مع الملفات البسيطة التي أنتجتها بنفسك. أما CSV في العالم الحقيقي ففيه حقول بين علامات اقتباس تحتوي على فواصل؛ استخدم مكتبة مثل CsvHelper لها.

لاحظ InvariantCulture في decimal.Parse: دونها تقرأ "4.50" على جهاز مضبوط على الألمانية أو البرتغالية النقطة كفاصل آلاف.

StreamWriter وStreamReader

تفتح دوال File الملف وتغلقه في كل استدعاء. لكتابة قطع كثيرة مع الوقت، أو لقراءة ملف أكبر من الذاكرة دون LINQ، افتح تدفقًا مرة واحدة. غلّفه دائمًا في using كي يُغلق حتى إن رُمي استثناء؛ فالكاتب غير المغلق قد لا يدفع مخزنه المؤقت أبدًا، وينتهي الملف فارغًا أو مبتورًا.

المخرجات:

2: WARN  disk 85% full
4: ERROR connection refused

تعيد ReadLine القيمة null في نهاية الملف، وهذا ما ينهي حلقة while. ويخزّن StreamWriter مخرجاته مؤقتًا ويكتبها إلى القرص حين يمتلئ المخزن، وحين تستدعي Flush()، وحين يُتخلّص منه؛ وتضمن كتلة using الأخيرة.

منذ C# 8 يتخلّص تصريح using من التدفق في نهاية الكتلة المحيطة، دون الأقواس الإضافية:

using var writer = new StreamWriter(path);   // C# 8: disposed when the method returns
writer.WriteLine("INFO  server started");

المسارات والمجلدات

ابنِ المسارات بـ Path.Combine بدل ضمّ النصوص. تُدرج الفاصل الصحيح لنظام التشغيل (\ على Windows و/ على Linux وmacOS) ولا تضاعفه. ويفكّك الصنف Path المسارات أيضًا، وينشئ Directory المجلدات ويسردها:

المخرجات:

february.txt, january.txt, march.txt
march.txt
march
.txt
march.pdf
False

فخ واحد: إن كان وسيط لاحق لـ Path.Combine جذريًا (يبدأ بـ / أو \، أو بحرف قرص على Windows) يُحذف كل ما قبله، فتكون Path.Combine("reports", "/2026") هي /2026. مرّر أجزاء نسبية، أو استخدم Path.Join (.NET Core 3.0 وما بعده)، التي لا تحذف شيئًا أبدًا.

تعيد Directory.GetFiles الملفات بأي ترتيب يعطيها به نظام الملفات، وهو يختلف بين Windows وLinux، فرتّب النتيجة حين يهمّ الترتيب. وDirectory.EnumerateFiles النسخة الكسولة، بالعلاقة نفسها بين ReadLines وReadAllLines. مرّر SearchOption.AllDirectories لتضمين المجلدات الفرعية.

معالجة الأخطاء

تفشل عمليات الملفات لأسباب خارج برنامجك: الملف مفقود، أو المجلد غير موجود، أو عملية أخرى تفتحه، أو القرص ممتلئ، أو الأذونات خاطئة. لكل منها نوع استثناء خاص. FileNotFoundException وDirectoryNotFoundException مشتقان من IOException، فالتقطهما قبله؛ أما UnauthorizedAccessException فليس مشتقًا منه، فيحتاج إلى catch خاصة به:

المخرجات:

missing file, using defaults
missing folder, using defaults
theme=dark

تبدو File.Exists قبل القراءة أبسط، لكنها لا تزيل الحاجة إلى try: قد يختفي الملف، أو يقفله برنامج آخر، في اللحظة بين الفحص والقراءة. استخدم Exists لتقرير ما تفعله، وcatch للنجاة مما يحدث فعلًا.

الترميز

تكتب File.WriteAllText وWriteAllLines وStreamWriter افتراضيًا UTF-8 دون علامة ترتيب البايتات (BOM)، ويكتشف القرّاء UTF-8 وUTF-16 وUTF-32 من علامة ترتيب البايتات إن وُجدت. مرّر Encoding حين يجب أن يكون الملف بصيغة معينة:

المخرجات:

plain.txt   16 bytes, reads back unchanged: True
bom.txt     19 bytes, reads back unchanged: True
utf16.txt   30 bytes, reads back unchanged: True

يأخذ é وã بايتين لكل منهما في UTF-8، ولهذا يحتاج 14 محرفًا إلى 16 بايت، وتُقرأ الملفات الثلاثة كلها بشكل صحيح لأن القارئ يتعرّف على علامة ترتيب البايتات. يبدو Encoding.UTF8 كالافتراضي لكنه يضيف علامة ترتيب البايتات، التي تعرضها بعض الأدوات (مستوردات CSV الأقدم وسكربتات الصدفة) كرموز غريبة في بداية السطر الأول؛ استخدم new UTF8Encoding(false) لـ UTF-8 صريح دونها. وقراءة ملف بترميز خاطئ لا ترمي استثناءً: تنتج محارف بديلة، فاعرف ترميز أي ملف لم تنشئه.

الإدخال والإخراج غير المتزامن للملفات

في تطبيقات الويب وشيفرة الواجهات يُهدر حجز خيط على إدخال وإخراج القرص ذلك الخيط. في .NET Core 2.0 وما بعده نسخ غير متزامنة من دوال File:

await File.WriteAllTextAsync("notes.txt", text);
string content = await File.ReadAllTextAsync("notes.txt");
string[] lines = await File.ReadAllLinesAsync("sales.csv");

وتعمل StreamReader.ReadLineAsync وStreamWriter.WriteLineAsync بالطريقة نفسها على التدفقات.

أخطاء شائعة

  • عدم التخلّص من تدفق. دون using قد لا يصل النص المخزّن مؤقتًا إلى القرص ويبقى الملف مقفلًا حتى يعمل جامع النفايات.
  • بناء المسارات بـ + "\\" +. ينكسر على Linux وmacOS. استخدم Path.Combine.
  • تحميل ملفات ضخمة بـ ReadAllText أو ReadAllLines. مرّر عليها بـ ReadLines أو StreamReader.
  • الاعتماد على المجلد الحالي. يعتمد على طريقة بدء البرنامج. استخدم مسارات مطلقة أو AppContext.BaseDirectory.
  • قراءة الأرقام من الملفات بثقافة الجهاز. مرّر CultureInfo.InvariantCulture لملفات البيانات.
  • توقّع أن تمنع File.Exists الاستثناءات. قد يتغيّر الملف بين الفحص والاستخدام.

الأسئلة الشائعة

كيف أكتب نصًا في ملف في C#؟

تنشئ File.WriteAllText("notes.txt", text); الملف، أو تكتب فوقه إن كان موجودًا، وتكتب النص بترميز UTF-8، وتغلقه. استخدم File.WriteAllLines(path, lines) لمجموعة أسطر وFile.AppendAllText(path, text) للإضافة إلى النهاية بدل الاستبدال. والثلاث كلها في System.IO.

كيف أقرأ ملفًا نصيًا في C#؟

تقرأ string text = File.ReadAllText("notes.txt"); الملف كله في نص واحد، وتقسمه string[] lines = File.ReadAllLines(path); إلى أسطر. وللملفات الكبيرة استخدم File.ReadLines(path) في foreach، التي تقرأ سطرًا واحدًا في كل مرة بدل تحميل كل شيء، أو StreamReader.

كيف أضيف إلى ملف في C#؟

تضيف File.AppendAllText(path, text) النص إلى نهاية الملف وتنشئ الملف إن لم يكن موجودًا؛ وتفعل File.AppendAllLines(path, lines) الشيء نفسه للأسطر. ومع التدفق افتحه في وضع الإضافة: new StreamWriter(path, append: true). تذكّر أن تضيف Environment.NewLine أو \n بنفسك حين تضيف نصًا.

كيف أقرأ ملفًا سطرًا سطرًا في C#؟

تمرّ foreach (string line in File.ReadLines(path)) { ... } على الملف سطرًا في كل مرة، فيبقى استخدام الذاكرة ثابتًا حتى لسجلات بحجم الغيغابايت. والمكافئ الأقدم StreamReader في كتلة using مع while ((line = reader.ReadLine()) != null). تجنّب ReadAllLines للملفات الكبيرة: تحمّل كل الأسطر في مصفوفة أولًا.

كيف أتحقق من وجود ملف في C#؟

تعيد File.Exists(path) القيمة true إن كان الملف موجودًا ويستطيع البرنامج رؤيته، وتفعل Directory.Exists(path) الشيء نفسه للمجلدات. لا تعتمد عليها لتجنّب الاستثناءات: قد يُحذف الملف أو يُقفل بين الفحص والقراءة، فعالج مع ذلك FileNotFoundException وIOException حول عملية الملف الفعلية.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن