معمل: تدفقات الملفات (File Streams) في C#
نظرة عامة
يشرح هذا المعمل كيفية التعامل مع تدفقات الملفات (File Streams) في C# من خلال بناء محاكي بسيط لسطر أوامر بأسلوب Linux يعمل على Windows. ستتعلم كيفية تنفيذ عمليات الملفات والمجلدات باستخدام مساحة الاسم System.IO، بما في ذلك القراءة من الملفات والكتابة إليها باستخدام التدفقات (Streams).
أهداف التعلم
بنهاية هذا المعمل، ستكون قادرًا على:
- فهم مفهوم التدفقات (Streams) في C#
- استخدام أصناف
FileStreamوStreamReaderوStreamWriter - تنفيذ عمليات على الملفات (إنشاء، قراءة، كتابة، إلحاق، حذف)
- تنفيذ عمليات على المجلدات (عرض، إنشاء)
- فهم خيارات
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أفضل الممارسات
- أغلِق التدفقات دائمًا: التدفقات غير المُغلَقة قد تقفل الملفات وتسبب تسرُّب الموارد (Resource Leaks)
- استخدم عبارة
using(أسلوب بديل):using (FileStream fs = new FileStream(path, FileMode.Open)) { // Stream automatically closed when leaving this block } - استدعِ
Flush()عند كتابة بيانات مهمة لضمان كتابتها فعليًا في القرص - تحقق من المدخلات قبل تنفيذ عمليات الملفات
- تعامل مع الاستثناءات (Exceptions) في الأكواد الإنتاجية (باستخدام كتل try-catch)
تمارين
- عدِّل أمر
nanoلاستخدام عبارةusingبدلًا من استدعاءاتClose()الصريحة - أضِف أمر
cpينسخ ملفًا من موقع إلى آخر باستخدام التدفقات - نفِّذ أمر
wcيحسب عدد الأسطر والكلمات والحروف في ملف - أضِف معالجة للاستثناءات إلى جميع عمليات الملفات
- أنشئ أمر
headيعرض أول N سطر من ملف - نفِّذ أمر
tailيعرض آخر N سطر من ملف
أهم النقاط
- توفر التدفقات (Streams) طريقة موحدة للتعامل مع البيانات المتسلسلة
- FileStream هو الصنف منخفض المستوى للتعامل مع إدخال/إخراج الملفات (File I/O)
- توفر StreamReader/StreamWriter عمليات نصية مريحة فوق طبقة التدفقات
- احرص دائمًا على إغلاق أو التخلص من (Dispose) التدفقات لتحرير موارد النظام
- تتحكم خيارات FileMode المختلفة في كيفية فتح الملفات أو إنشائها
- التخزين المؤقت (Buffering) يُحسِّن الأداء، لكنه يتطلب
Flush()للكتابة الفورية