Lesson ১৪৭ মিনিট পড়া

ফাইল ইনপুট/আউটপুট বা I/O (File I/O)

ফাইল পড়া এবং লেখা — আপনার প্রোগ্রামের দীর্ঘমেয়াদী স্মৃতি (long-term memory)

ফাইল কেন এত গুরুত্বপূর্ণ? (Why Files Matter)

যখনই আপনার প্রোগ্রামটি শেষ হয়ে যায় বা বন্ধ হয়, তখন এর ভেরিয়েবলগুলো (variables) মুছে যায় — কারণ এগুলো র‍্যামের (RAM) মধ্যে থাকে, যা প্রোগ্রাম বন্ধ হওয়ার সাথে সাথেই পরিষ্কার হয়ে যায়। কিন্তু ফাইল হলো আপনার প্রোগ্রামের দীর্ঘমেয়াদী স্মৃতি (long-term memory)। ফাইলের সাহায্যে আপনি হার্ডডিস্কে ডেটা সেভ করে রাখতে পারবেন, যার ফলে প্রোগ্রাম রিস্টার্ট (restart) দিলে, ক্র্যাশ (crash) করলে, এমনকি বিদ্যুৎ চলে গেলেও আপনার ডেটা হারাবে না।

সি শার্পে (C#) ফাইল নিয়ে কাজ করাটা আশ্চর্যজনক রকম সহজ। সাধারণ কাজগুলো করার জন্য File ক্লাসে খুব সহজ কিছু এক লাইনের (one-liner) মেথড রয়েছে, আবার বড় ফাইলগুলোর জন্য রয়েছে StreamReader বা StreamWriter, যার মাধ্যমে পুরো ফাইলের ওপর আপনার দারুণ নিয়ন্ত্রণ থাকে।

ফাইল নিয়ে সব ধরনের কাজই System.IO নামের একটি নেমস্পেসের (namespace) ভেতরে থাকে (যা টপ-লেভেল স্টেটমেন্টস বা top-level statements-এ আগে থেকেই ডিফল্টভাবে যুক্ত করা থাকে)।

File ক্লাসের সাহায্যে খুব দ্রুত ফাইলের কাজগুলো করে ফেলা (Quick File Operations with the File Class)

string path = "hello.txt";
// Write an entire file in one line
File.WriteAllText(path, "Hello from C#!\nThis is line 2.");
Console.WriteLine("File written!");
// Read the entire file in one line
string content = File.ReadAllText(path);
Console.WriteLine($"Content:\n{content}");
// Write lines (array of strings)
string[] shoppingList = { "Apples", "Bread", "Milk", "Eggs" };
File.WriteAllLines("shopping.txt", shoppingList);
// Read lines back as array
string[] lines = File.ReadAllLines("shopping.txt");
Console.WriteLine($"\nShopping list ({lines.Length} items):");
for (int i = 0; i < lines.Length; i++)
Console.WriteLine($" {i + 1}. {lines[i]}");
// Append to a file (adds to the end without erasing)
File.AppendAllText("shopping.txt", "\nCheese\nButter");
Console.WriteLine($"\nAfter appending:");
Console.WriteLine(File.ReadAllText("shopping.txt"));
// Check if file exists
Console.WriteLine($"\nhello.txt exists: {File.Exists(path)}");
Console.WriteLine($"ghost.txt exists: {File.Exists("ghost.txt")}");
Output
File written!
Content:
Hello from C#!
This is line 2.

Shopping list (4 items):
  1. Apples
  2. Bread
  3. Milk
  4. Eggs

After appending:
Apples
Bread
Milk
Eggs
Cheese
Butter

hello.txt exists: True
ghost.txt exists: False

StreamReader এবং StreamWriter — বড় ফাইলগুলোর জন্য (StreamReader & StreamWriter — For Bigger Files)

File.ReadAllText() মেথডটি পুরো ফাইলটিকে একবারেই পড়ে মেমোরিতে (memory) নিয়ে আসে। ছোট ফাইলগুলোর জন্য এটি একদম ঠিক আছে, কিন্তু আপনার ফাইলটি যদি ২ গিগাবাইট (2 GB) সাইজের হয়, তখন কী হবে? এটি আপনার কম্পিউটারের পুরো র‍্যাম (RAM) খেয়ে ফেলবে এবং আপনার অ্যাপটি ক্র্যাশ (crash) করবে।

এ ক্ষেত্রে বালতির (bucket) বদলে StreamReader এবং StreamWriter অনেকটা বাগানে পানি দেওয়ার পাইপের (garden hose) মতো কাজ করে। একবারে বালতি ভর্তি করে সব পানি (বা ডেটা) না ঢেলে, এগুলো একটি পাইপ বা স্ট্রিমের (stream) ভেতর দিয়ে একে একে পানি প্রবাহিত করে। এর ফলে মেমোরি অনেক বেঁচে যায়।

এখানে using স্টেটমেন্টটি খুবই গুরুত্বপূর্ণ — কারণ কাজ শেষ হয়ে গেলে এটি নিজে থেকেই ফাইলটিকে বন্ধ করে দেয়, এমনকি কোনো এক্সেপশন বা এরর (error) হলেও। একে একটি ছোট্ট চিরকুট হিসেবে ভাবতে পারেন, যাতে লেখা আছে "আপনার কাজ শেষ হয়ে গেলে, দয়া করে জিনিসটি আবার তার জায়গায় রেখে দিন।"

StreamReader, StreamWriter এবং using (StreamReader, StreamWriter & using)

// Write with StreamWriter + using statement
using (var writer = new StreamWriter("journal.txt"))
{
writer.WriteLine("Day 1: Started learning C#");
writer.WriteLine("Day 2: Files are actually fun!");
writer.WriteLine("Day 3: Built my first app");
}
// File is automatically closed here, even if an error occurred
// Read with StreamWriter — line by line
Console.WriteLine("Journal entries:");
using (var reader = new StreamReader("journal.txt"))
{
string? line;
int lineNum = 1;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine($" {lineNum}: {line}");
lineNum++;
}
}
// Modern C# shorthand: using declaration (no braces needed)
using var writer2 = new StreamWriter("notes.txt");
writer2.WriteLine("This using style disposes at end of scope");
writer2.WriteLine("Cleaner for simple cases!");
// writer2 is disposed when the enclosing scope ends
Console.WriteLine($"\nNotes: {File.ReadAllText("notes.txt")}");
Output
Journal entries:
  1: Day 1: Started learning C#
  2: Day 2: Files are actually fun!
  3: Day 3: Built my first app

Notes: This using style disposes at end of scope
Cleaner for simple cases!

অ্যাসিংক ফাইল I/O — আপনার অ্যাপকে আটকে বা ফ্রিজ হতে দেবেন না (Async File I/O — Don't Freeze Your App)

ফাইল সংক্রান্ত কাজগুলো একটু ধীরগতির (slow) হতে পারে — বিশেষ করে পুরোনো হার্ড ড্রাইভ (hard drives) বা নেটওয়ার্কের (networks) ওপর কাজ করার সময়। আপনি যদি মেইন থ্রেডে (main thread) বসে একটি বিশাল ফাইল পড়তে যান, তবে ফাইলের কাজ শেষ হওয়ার জন্য অপেক্ষা করতে করতে আপনার অ্যাপটি একেবারে আটকে বা ফ্রিজ (freezes) হয়ে যাবে। আর কেউই এমন আটকে থাকা বা কাজ না করা অ্যাপ ব্যবহার করতে চায় না।

অ্যাসিংক (Async) মেথডগুলো (যেগুলোতে async/await ব্যবহার করা হয়) ফাইল পড়ার সময়ও আপনার প্রোগ্রামকে অন্যান্য কাজ করার সুযোগ দেয়। File ক্লাসের প্রতিটি মেথডেরই অ্যাসিংক বা Async ভার্সন রয়েছে — এগুলোর জন্য শুধু মেথডের নামের শেষে Async শব্দটি যুক্ত করে দিতে হয় এবং কল (call) করার সময় সামনে await লিখে দিতে হয়।

অ্যাসিংক ফাইল অপারেশন এবং রোজকার জীবনের কিছু উদাহরণ (Async File Operations & Practical Examples)

// Async file operations
await File.WriteAllTextAsync("async-test.txt", "Written asynchronously!");
string asyncContent = await File.ReadAllTextAsync("async-test.txt");
Console.WriteLine(asyncContent);
// Practical example: simple config reader
var config = new Dictionary<string, string>();
string configData = "theme=dark\nlanguage=en\nfont_size=14\nauto_save=true";
await File.WriteAllTextAsync("config.ini", configData);
string[] configLines = await File.ReadAllLinesAsync("config.ini");
foreach (string line in configLines)
{
string[] parts = line.Split('=', 2);
if (parts.Length == 2)
config[parts[0].Trim()] = parts[1].Trim();
}
Console.WriteLine("\nConfig loaded:");
foreach (var pair in config)
Console.WriteLine($" {pair.Key} = {pair.Value}");
// Working with directories
string dir = "my_data";
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
await File.WriteAllTextAsync(Path.Combine(dir, "test.txt"), "Inside a folder!");
Console.WriteLine($"\nFiles in {dir}:");
foreach (string file in Directory.GetFiles(dir))
Console.WriteLine($" {Path.GetFileName(file)}");
Output
Written asynchronously!

Config loaded:
  theme = dark
  language = en
  font_size = 14
  auto_save = true

Files in my_data:
  test.txt
Note: 🔐 স্ট্রিমের (streams) সাথে সব সময় 'using' ব্যবহার করুন! যদি আপনি এটি ব্যবহার করতে ভুলে যান, তবে ফাইলটি লক (locked) হয়ে থাকবে — সে ক্ষেত্রে গারবেজ কালেকশন (garbage collection) এসে ফাইলটিকে পরিষ্কার না করা পর্যন্ত অন্য কোনো প্রোগ্রাম (এমনকি আপনার নিজের কোডও) ওই ফাইলটি আর ব্যবহার করতে পারবে না। কোনো এক্সেপশন (exception) বা এরর থ্রো করলে বা দেখা দিলেও, 'using' স্টেটমেন্ট এই ক্লিনআপ বা পরিষ্কার করার গ্যারান্টি দেয়।
চ্যালেঞ্জ

ছোট কুইজ

File.WriteAllText এবং File.AppendAllText এর মধ্যে মূল পার্থক্য কী?
Exception HandlingDelegates, Events & Lambdas