# קבצים — File, StreamReader ו-StreamWriter📖 שיעור

## מה זה?

עד עכשיו כל הנתונים בתוכניות שלנו נעלמו ברגע שהתוכנית נסגרה — משתנים חיים רק בזיכרון RAM בזמן ריצה. כדי לשמור נתונים **לאורך זמן** (בין הרצות), צריך לכתוב אותם לקובץ על הדיסק. C# נותנת כמה רמות גישה: `File` למבצעים פשוטים וקצרים, `StreamReader`/`StreamWriter` לשליטה מדויקת יותר, במיוחד עם קבצים גדולים.

## מילות מפתח שחשוב לזכור

• `File.ReadAllText` / `File.WriteAllText` — הדרך הפשוטה ביותר: קורא/כותב את **כל** תוכן הקובץ בפעולה אחת

• `StreamReader` / `StreamWriter` — קריאה/כתיבה שורה-שורה או בזרימה, יעילים יותר לקבצים גדולים

• `using` statement — מבטיח שמשאב (כמו קובץ פתוח) ייסגר אוטומטית, גם אם נזרקת חריגה באמצע

• `File.Exists` — בודק אם קובץ קיים לפני שמנסים לגשת אליו

```csharp
// 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");
```

```csharp
// 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);
    }
}
```

```csharp
// 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](https://learn.microsoft.com/dotnet/standard/io/)

[Microsoft Learn — using statement](https://learn.microsoft.com/dotnet/csharp/language-reference/statements/using)

---

## תרגילים

### תרגיל 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` מסיים.

**דרישות:**
1. כל הערה נשמרת מיד לקובץ `notes.txt` עם `StreamWriter` (`append: true`) ברגע ההוספה — לא רק בזיכרון
2. פקודת `list` קוראת את **כל** הקובץ מחדש עם `File.ReadAllLines` ומדפיסה ממוספר
3. טיפול ב-`IOException` סביב כל פעולת קובץ, עם הודעת שגיאה ברורה אם הכתיבה/קריאה נכשלת

**בדיקה:** הוספת 3 הערות ואז `list` מציגה את כל השלוש; סגירת התוכנית ופתיחתה מחדש עם `list` עדיין מציגה את ההערות — הוכחה שהן נשמרו לדיסק, לא רק לזיכרון.

## מה בפרק הבא

בפרק הבא — **JSON**: איך הופכים אובייקטים ב-C# לפורמט טקסט סטנדרטי לשמירה/העברה (Serialize), ובחזרה (Deserialize) — הבסיס לכל תקשורת עם שרתים ו-APIs.
