קבצים — File, StreamReader ו-StreamWriter
מה זה?
עד עכשיו כל הנתונים בתוכניות שלנו נעלמו ברגע שהתוכנית נסגרה — משתנים חיים רק בזיכרון RAM בזמן ריצה. כדי לשמור נתונים לאורך זמן (בין הרצות), צריך לכתוב אותם לקובץ על הדיסק. C# נותנת כמה רמות גישה: File למבצעים פשוטים וקצרים, StreamReader/StreamWriter לשליטה מדויקת יותר, במיוחד עם קבצים גדולים.
מילות מפתח שחשוב לזכור
• File.ReadAllText / File.WriteAllText — הדרך הפשוטה ביותר: קורא/כותב את כל תוכן הקובץ בפעולה אחת
• StreamReader / StreamWriter — קריאה/כתיבה שורה-שורה או בזרימה, יעילים יותר לקבצים גדולים
• using statement — מבטיח שמשאב (כמו קובץ פתוח) ייסגר אוטומטית, גם אם נזרקת חריגה באמצע
• File.Exists — בודק אם קובץ קיים לפני שמנסים לגשת אליו
// File — the simple way, for small files
File.WriteAllText("notes.txt", "Hello from the file!");
string content = File.ReadAllText("notes.txt");
Console.WriteLine(content); // Hello from the file!
// writing/reading several lines at once
File.WriteAllLines("names.txt", new[] { "Dana", "Avi", "Rotem" });
string[] names = File.ReadAllLines("names.txt");// StreamWriter/StreamReader with using — precise control, and guaranteed closing
using (StreamWriter writer = new StreamWriter("log.txt", append: true))
{
writer.WriteLine($"{DateTime.Now}: action performed");
} // the file closes automatically at the end of the block, even if an exception occurred
using (StreamReader reader = new StreamReader("log.txt"))
{
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
}// combined with try/catch — the file might not exist, or might not be accessible
try
{
if (!File.Exists("data.txt"))
{
Console.WriteLine("File does not exist");
return;
}
string data = File.ReadAllText("data.txt");
Console.WriteLine(data);
}
catch (IOException ex)
{
Console.WriteLine($"Read error: {ex.Message}");
}הסבר עיקרי
File מול StreamReader/StreamWriter — File.ReadAllText נוח כשהקובץ קטן וסביר לטעון אותו כולו לזיכרון בבת אחת. StreamReader קורא שורה-שורה (ReadLine) בלי לטעון את כל הקובץ לזיכרון בבת אחת — קריטי לקבצים גדולים (למשל קובץ נתונים של מיליוני שורות, כמו שנפגוש בפרויקט המסכם של היחידה).
using פותר בעיה אמיתית — קובץ פתוח הוא משאב חיצוני (Handle) שמערכת ההפעלה מנהלת; אם שוכחים לסגור אותו, הוא נשאר “תפוס” גם אחרי שהתוכנית סיימה להשתמש בו — עלול לחסום גישה עתידית. using מבטיח קריאה אוטומטית ל-Dispose() (שסוגר את הקובץ) ברגע שהבלוק מסתיים — גם אם נזרקה חריגה באמצע. זו למעשה גרסה מובנית ל-finally שכבר תרגלנו, ייעודית למשאבים.
File.Exists לפני גישה — קובץ יכול “להיעלם” בין רגע לרגע (נמחק, נייד חיצוני שהוצא) — File.Exists בודק לפני שמנסים לגשת, אבל גם בדיקה כזו לא מבטלת את הצורך ב-try/catch, כי מצב יכול להשתנות בין הבדיקה לגישה בפועל (Race Condition) — שילוב שניהם הוא הפרקטיקה הבטוחה.
יתרונות
File.ReadAllText/WriteAllText נותנים תחביר קצר לפעולות פשוטות; StreamReader/StreamWriter יעילים בזיכרון לקבצים גדולים כי לא טוענים הכל בבת אחת; using מבטיח סגירת משאבים אוטומטית גם בתרחישי שגיאה, בלי finally ידני בכל מקום.
חסרונות
File.ReadAllText על קובץ ענק (למשל כמה ג’יגה-בייט) עלול לצרוך זיכרון רב מיותר; פעולות קובץ תמיד עלולות להיכשל מסיבות חיצוניות (הרשאות, קובץ נעול על ידי תהליך אחר, דיסק מלא) — דורש טיפול-שגיאות תמיד, לא רק “לפעמים”.
נקודות חשובות
• File.ReadAllText/WriteAllText = פעולה שלמה בבת אחת; StreamReader/StreamWriter = שורה-שורה, יעיל לקבצים גדולים
• using מבטיח Dispose() אוטומטי (סגירת משאב) גם כשנזרקת חריגה — כמו finally מובנה
• File.Exists לא מבטל צורך ב-try/catch — מצב הקובץ יכול להשתנות בין הבדיקה לגישה
• פעולות קובץ הן תמיד מקור פוטנציאלי ל-IOException — יש לצפות לזה תמיד
טעויות נפוצות
• לפתוח StreamWriter/StreamReader בלי using ולשכוח לסגור — משאב “תקוע” פתוח
• לסמוך רק על File.Exists בלי try/catch, ולהתעלם מהאפשרות שהמצב משתנה בין הבדיקה לגישה בפועל
• לטעון קובץ ענק שלם עם File.ReadAllText כשדרושה רק קריאה חלקית — בזבוז זיכרון מיותר
סיכום
File.ReadAllText/WriteAllText נותנים גישה פשוטה לקבצים קטנים בפעולה אחת; StreamReader/StreamWriter נותנים שליטה מדויקת יותר, יעילה לקבצים גדולים, קריאה/כתיבה שורה-שורה. using מבטיח סגירה אוטומטית ובטוחה של הקובץ גם בתרחיש חריגה, ופעולות קובץ תמיד דורשות try/catch — קבצים הם משאב חיצוני שעלול להיכשל מסיבות שאין לתוכנית שליטה עליהן.
דוקומנטציה רשמית
Microsoft Learn — File and Stream I/O
Microsoft Learn — using statement
תרגילים
תרגיל 1 — כתיבה וקריאה בסיסית
המשימה: כתבו לקובץ todo.txt שלוש משימות עם File.WriteAllLines, ואז קראו אותן חזרה עם File.ReadAllLines והדפיסו כל אחת עם מספר סידורי.
בדיקה: שלוש השורות מודפסות בסדר הנכון עם מספור.
תרגיל 2 — StreamWriter עם append
המשימה: כתבו פונקציה LogAction(string message) שמוסיפה שורה לקובץ activity.log עם StreamWriter ו-append: true, כולל using. קראו לה 3 פעמים עם הודעות שונות.
בדיקה: קובץ activity.log מכיל את כל 3 השורות (לא רק האחרונה) — הוכחה ש-append: true עובד נכון.
פרויקט מסכם
המשימה: בנו “מערכת גיבוי הערות” פשוטה — התוכנית קוראת פקודות מהמשתמש בלולאה: add <טקסט> מוסיף הערה, list מציג את כולן, exit מסיים.
דרישות:
- כל הערה נשמרת מיד לקובץ
notes.txtעםStreamWriter(append: true) ברגע ההוספה — לא רק בזיכרון - פקודת
listקוראת את כל הקובץ מחדש עםFile.ReadAllLinesומדפיסה ממוספר - טיפול ב-
IOExceptionסביב כל פעולת קובץ, עם הודעת שגיאה ברורה אם הכתיבה/קריאה נכשלת
בדיקה: הוספת 3 הערות ואז list מציגה את כל השלוש; סגירת התוכנית ופתיחתה מחדש עם list עדיין מציגה את ההערות — הוכחה שהן נשמרו לדיסק, לא רק לזיכרון.
מה בפרק הבא
בפרק הבא — JSON: איך הופכים אובייקטים ב-C# לפורמט טקסט סטנדרטי לשמירה/העברה (Serialize), ובחזרה (Deserialize) — הבסיס לכל תקשורת עם שרתים ו-APIs.