Logo

معمل: تدفقات الملفات (File Streams) في C#

8 دقائق قراءة
شرائح الدرس
1 / 14

المعمل: تدفقات الملفات (File Streams) في C#

بناء محاكي ملفات على طراز لينكس باستخدام FileStream وStreamReader وStreamWriter

نظرة عامة

يشرح هذا المعمل كيفية التعامل مع تدفقات الملفات (File Streams) في C# من خلال بناء محاكي بسيط لسطر أوامر بأسلوب Linux يعمل على Windows. ستتعلم كيفية تنفيذ عمليات الملفات والمجلدات باستخدام مساحة الاسم System.IO، بما في ذلك القراءة من الملفات والكتابة إليها باستخدام التدفقات (Streams).


أهداف التعلم

بنهاية هذا المعمل، ستكون قادرًا على:

  1. فهم مفهوم التدفقات (Streams) في C#
  2. استخدام أصناف FileStream وStreamReader وStreamWriter
  3. تنفيذ عمليات على الملفات (إنشاء، قراءة، كتابة، إلحاق، حذف)
  4. تنفيذ عمليات على المجلدات (عرض، إنشاء)
  5. فهم خيارات FileMode وFileAccess المختلفة

ما هو التدفق (Stream)؟

التدفق (Stream) هو تجريد (Abstraction) يمثل تسلسلًا من البايتات (Bytes). في C#، تُستخدم التدفقات للقراءة من مصادر بيانات مختلفة والكتابة إليها، مثل:

  • الملفات الموجودة على القرص
  • اتصالات الشبكة
  • المخازن المؤقتة في الذاكرة (Memory Buffers)

يُعَد الصنف System.IO.Stream الصنف الأساسي لكل التدفقات في .NET. ومن أشهر الأصناف المشتقة منه:

  • FileStream: للقراءة من الملفات والكتابة إليها
  • MemoryStream: للبيانات الموجودة في الذاكرة
  • NetworkStream: لبيانات الشبكة

معمارية البرنامج

flowchart TD
    A[Start Program] --> B[Display Welcome Message]
    B --> C{Read User Command}
    C -->|pwd| D[Show Current Directory]
    C -->|ls| E[List Files & Directories]
    C -->|mkdir| F[Create Directory]
    C -->|touch| G[Create Empty File]
    C -->|rm| H[Delete File]
    C -->|cat| I[Read & Display File]
    C -->|nano| J[Edit File Content]
    C -->|nano-append| K[Append to File]
    C -->|exit| L[Exit Program]
    C -->|invalid| M[Show Error]
    D --> C
    E --> C
    F --> C
    G --> C
    H --> C
    I --> C
    J --> C
    K --> C
    M --> C
    L --> N[End Program]

شرح الكود

1. حلقة البرنامج الرئيسية (Main Program Loop)

يعرض التابع (Method) الرئيسي محثًّا (Prompt) ويستمر في قراءة أوامر المستخدم حتى يكتب المستخدم exit.

while (!exit)
{
    Console.Write("mmabas@SDOps:~$ ");
    string input = Console.ReadLine() ?? "";
    // Command processing via switch statement
}

2. عمليات المجلدات

pwd - طباعة المجلد الحالي

يعرض مسار المجلد الحالي وبياناته الوصفية (Metadata):

public static void Pwd()
{
    var currentDir = Directory.GetCurrentDirectory();
    DirectoryInfo dirinfo = new DirectoryInfo(currentDir);
    Console.WriteLine(dirinfo.FullName);
    Console.WriteLine("Full Name of the directory is : {0}", dirinfo.FullName);
    Console.WriteLine("The directory was last accesses on: {0}", dirinfo.LastAccessTime.ToString());
}

ls - عرض محتويات المجلد

يعرض جميع الملفات والمجلدات الفرعية الموجودة في المجلد الحالي:

public static void Ls()
{
    var currentDir = Directory.GetCurrentDirectory();
    DirectoryInfo dirinfo = new DirectoryInfo(currentDir);
    
    FileInfo[] filesInDir = dirinfo.GetFiles();
    DirectoryInfo[] dirsInDir = dirinfo.GetDirectories();
    
    foreach (var directoryInfo in dirsInDir)
    {
        Console.WriteLine("{0}      -----      {1}", directoryInfo.Name, directoryInfo.LastAccessTime);
    }
    foreach (FileInfo file in filesInDir)
    {
        Console.WriteLine("{0}      {1}      {2}", file.Name, file.Length, file.LastAccessTime);
    }
}

mkdir - إنشاء مجلد

ينشئ مجلدًا جديدًا إن لم يكن موجودًا بالفعل:

public static void MkDir()
{
    Console.Write("Enter the name of the directory: ");
    var name = Console.ReadLine();
    
    if (string.IsNullOrEmpty(name))
    {
        Console.WriteLine("Directory name cannot be empty.");
        return;
    }
    
    if (Directory.Exists(name))
    {
        Console.WriteLine("Directory already exists.");
        return;
    }
    
    Directory.CreateDirectory(name);
}

3. عمليات الملفات

touch - إنشاء ملف فارغ

ينشئ ملفًا جديدًا فارغًا:

public static void Touch()
{
    Console.Write("Enter the name of the file: ");
    var name = Console.ReadLine();
    
    if (string.IsNullOrEmpty(name))
    {
        Console.WriteLine("File name cannot be empty.");
        return;
    }
    
    if (File.Exists(name))
    {
        Console.WriteLine("File already exists.");
        return;
    }
    
    var fs = File.Create(name);
    fs.Close();  // Important: Always close the stream after use
}

ملاحظة: من المهم إغلاق FileStream بعد إنشائه لتحرير مقبض الملف (File Handle).

rm - حذف ملف

يحذف ملفًا بعد التحقق منه:

private static void Rm()
{
    Console.Write("Enter the name of the file: ");
    var name = Console.ReadLine();
    if (!ValidateExists(name)) return;
    
    File.Delete(name);
}

4. عمليات التدفقات (Stream Operations)

cat - قراءة الملف وعرضه

يقرأ محتوى الملف بالكامل ويعرضه:

private static void Cat()
{
    Console.Write("Enter the name of the file: ");
    var name = Console.ReadLine();
    if (!ValidateExists(name)) return;
    
    var content = File.ReadAllText(name);
    Console.WriteLine(content);
}

ملاحظة: التابع File.ReadAllText() تابع مساعد (Convenience Method) يستخدم التدفقات داخليًا.

nano-append - إلحاق نص بالملف

يُلحق نصًا جديدًا بنهاية ملف موجود:

private static void NanoAppend()
{
    Console.Write("Enter the name of the file: ");
    var name = Console.ReadLine();
    if (!ValidateExists(name)) return;
    
    Console.Write("Enter the text to append: ");
    var text = Console.ReadLine();
    
    if (string.IsNullOrEmpty(text))
    {
        Console.WriteLine("Text cannot be empty.");
        return;
    }
    
    File.AppendAllText(name, text);
}

nano - تعديل محتوى الملف

هذه أهم دالة لفهم التدفقات؛ فهي تقرأ المحتوى القديم أولًا، ثم تكتب المحتوى الجديد:

private static void Nano()
{
    Console.Write("Enter the name of the file: ");
    var name = Console.ReadLine();
    if (!ValidateExists(name)) return;
    
    Console.WriteLine("----- ------ ----- [Old Content] ----- ------ -----");
    ReadFileData(name);
    
    Console.WriteLine("----- ------ ----- [Please write the new content] ----- ------ -----");
    WriteFileData(name);
}

5. التعامل مع التدفقات مباشرةً

القراءة باستخدام التدفقات

private static void ReadFileData(string? name)
{
    FileStream fs = new FileStream(name, FileMode.OpenOrCreate, FileAccess.ReadWrite);
    StreamReader sr = new StreamReader(fs);
    sr.BaseStream.Seek(0, SeekOrigin.Begin);  // Move to start of file
    
    string str = sr.ReadLine();
    while (str != null)
    {
        Console.WriteLine("{0}", str);
        str = sr.ReadLine();
    }
    
    sr.Close();
    fs.Close();
}

المفاهيم الأساسية:

  • FileMode.OpenOrCreate: يفتح الملف إن كان موجودًا، وينشئه إن لم يكن كذلك
  • FileAccess.ReadWrite: يسمح بالقراءة والكتابة معًا
  • Seek(): ينقل موضع التدفق (Stream Position) إلى موقع محدد
  • SeekOrigin.Begin: يحدد بداية التدفق

الكتابة باستخدام التدفقات

private static void WriteFileData(string? name)
{
    FileStream fs = new FileStream(name, FileMode.Create, FileAccess.ReadWrite);
    StreamWriter w = new StreamWriter(fs);
    
    var text = Console.ReadLine();
    if (string.IsNullOrEmpty(text))
    {
        Console.WriteLine("Text cannot be empty.");
        return;
    }
    
    w.Write(text);
    w.Flush();  // Ensures data is written to disk
    w.Close();
    fs.Close();
}

المفاهيم الأساسية:

  • FileMode.Create: ينشئ ملفًا جديدًا أو يستبدل الملف الموجود
  • Flush(): يجبر البيانات المخزَّنة مؤقتًا (Buffered) على الكتابة في الملف
  • أغلق التدفقات دائمًا بالترتيب الصحيح: StreamWriter أولًا، ثم FileStream

خيارات FileMode

الوضع (Mode)الوصف
CreateNewينشئ ملفًا جديدًا؛ ويطلق استثناءً (Exception) إذا كان الملف موجودًا بالفعل
Createينشئ ملفًا جديدًا؛ ويستبدل الملف الموجود إن وُجد
Openيفتح ملفًا موجودًا؛ ويطلق استثناءً إذا لم يكن الملف موجودًا
OpenOrCreateيفتح الملف إن كان موجودًا، وينشئ ملفًا جديدًا إن لم يكن كذلك
Truncateيفتح الملف الموجود ويمسح محتواه
Appendيفتح الملف وينتقل إلى نهايته للإلحاق

خيارات FileAccess

الوصول (Access)الوصف
Readوصول للقراءة فقط
Writeوصول للكتابة فقط
ReadWriteوصول للقراءة والكتابة معًا

سير عمل التدفق (Stream Workflow)

sequenceDiagram
    participant App as Application
    participant FS as FileStream
    participant SR as StreamReader/Writer
    participant File as Physical File
    
    App->>FS: Create FileStream(path, mode, access)
    FS->>File: Open file handle
    App->>SR: Create StreamReader/Writer(FileStream)
    SR->>FS: Connect to stream
    
    alt Reading
        App->>SR: ReadLine()
        SR->>FS: Read bytes
        FS->>File: Read from disk
        File-->>FS: Return bytes
        FS-->>SR: Return bytes
        SR-->>App: Return string
    else Writing
        App->>SR: Write(text)
        SR->>FS: Write bytes to buffer
        App->>SR: Flush()
        SR->>FS: Force write
        FS->>File: Write to disk
    end
    
    App->>SR: Close()
    SR->>FS: Release stream
    App->>FS: Close()
    FS->>File: Release file handle

دالة التحقق المساعدة (Validation Helper)

يستخدم البرنامج تابعًا للتحقق من وجود الملفات:

private static bool ValidateExists(string? name)
{
    if (string.IsNullOrEmpty(name))
    {
        Console.WriteLine("File name cannot be empty.");
        return false;
    }
    
    if (!File.Exists(name))
    {
        Console.WriteLine("File does not exist.");
        return false;
    }
    
    return true;
}

أمثلة على الاستخدام

جلسة تشغيل نموذجية

Welcome to Linux emulation on Windows!
 
mmabas@SDOps:~$ pwd
C:\Users\Student\Projects
Full Name of the directory is : C:\Users\Student\Projects
The directory was last accesses on: 10/26/2025 2:30:00 PM
 
mmabas@SDOps:~$ touch myfile.txt
 
mmabas@SDOps:~$ nano
Enter the name of the file: myfile.txt
----- ------ ----- [Old Content] ----- ------ -----
----- ------ ----- [Please write the new content] ----- ------ -----
Hello, this is my first file!
 
mmabas@SDOps:~$ cat
Enter the name of the file: myfile.txt
Hello, this is my first file!
 
mmabas@SDOps:~$ nano-append
Enter the name of the file: myfile.txt
Enter the text to append: This is appended text.
 
mmabas@SDOps:~$ cat
Enter the name of the file: myfile.txt
Hello, this is my first file!This is appended text.
 
mmabas@SDOps:~$ exit

أفضل الممارسات

  1. أغلِق التدفقات دائمًا: التدفقات غير المُغلَقة قد تقفل الملفات وتسبب تسرُّب الموارد (Resource Leaks)
  2. استخدم عبارة using (أسلوب بديل):
    using (FileStream fs = new FileStream(path, FileMode.Open))
    {
        // Stream automatically closed when leaving this block
    }
  3. استدعِ Flush() عند كتابة بيانات مهمة لضمان كتابتها فعليًا في القرص
  4. تحقق من المدخلات قبل تنفيذ عمليات الملفات
  5. تعامل مع الاستثناءات (Exceptions) في الأكواد الإنتاجية (باستخدام كتل try-catch)

تمارين

  1. عدِّل أمر nano لاستخدام عبارة using بدلًا من استدعاءات Close() الصريحة
  2. أضِف أمر cp ينسخ ملفًا من موقع إلى آخر باستخدام التدفقات
  3. نفِّذ أمر wc يحسب عدد الأسطر والكلمات والحروف في ملف
  4. أضِف معالجة للاستثناءات إلى جميع عمليات الملفات
  5. أنشئ أمر head يعرض أول N سطر من ملف
  6. نفِّذ أمر tail يعرض آخر N سطر من ملف

أهم النقاط

  • توفر التدفقات (Streams) طريقة موحدة للتعامل مع البيانات المتسلسلة
  • FileStream هو الصنف منخفض المستوى للتعامل مع إدخال/إخراج الملفات (File I/O)
  • توفر StreamReader/StreamWriter عمليات نصية مريحة فوق طبقة التدفقات
  • احرص دائمًا على إغلاق أو التخلص من (Dispose) التدفقات لتحرير موارد النظام
  • تتحكم خيارات FileMode المختلفة في كيفية فتح الملفات أو إنشائها
  • التخزين المؤقت (Buffering) يُحسِّن الأداء، لكنه يتطلب Flush() للكتابة الفورية