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

এক্সেপশন হ্যান্ডেলিং (Exception Handling)

অপ্রত্যাশিত পরিস্থিতির জন্য প্রস্তুত থাকুন — আপনার অ্যাপ (app) ক্র্যাশ বা বন্ধ হওয়ার আগেই এরর বা ত্রুটিগুলোকে ধরে ফেলুন

জিনিসপত্র কেন কাজ করে না বা নষ্ট হয়? (Why Things Go Wrong)

প্রোগ্রামগুলো সহজেই ক্র্যাশ (crash) করতে পারে বা নষ্ট হতে পারে। অনেক সময় ফাইলগুলো হারিয়ে যায়। আপনি হয়তো একটি নম্বরের জন্য জিজ্ঞাসা করেছেন, কিন্তু ব্যবহারকারী (user) সেখানে লিখে দিয়েছে "জাম"। আবার হয়তো কোনো কিছু ডাউনলোড করার মাঝপথেই ইন্টারনেট সংযোগ বিচ্ছিন্ন হয়ে গেছে। একজন দক্ষ ডেভেলপার শুধু কাজ চালানোর মতো কোডই লেখেন না — বরং তিনি এমন কোড লেখেন যা কোনো ত্রুটি (error) হলেও সুন্দরভাবে পরিস্থিতি সামাল দিতে পারে (fails gracefully)

সি শার্পে (C#) যখন কোনো কিছু ঠিকমতো কাজ করে না, তখন সিস্টেমটি একটি এক্সেপশন (exception) বা সমস্যা থ্রো (throw) করে বা ছুঁড়ে দেয় — এটি অনেকটা ফায়ার অ্যালার্ম বা বিপদের ঘণ্টা বাজানোর মতো। যদি কেউ সেই অ্যালার্মটিকে নিয়ন্ত্রণ না করে (বা এক্সেপশনটিকে ক্যাচ (catch) না করে), তবে ভবনের সবাই আতঙ্কে বেরিয়ে পড়বে (অর্থাৎ আপনার প্রোগ্রামটি ক্র্যাশ করবে)। কিন্তু আপনি যদি একটি হ্যান্ডলার (handler) তৈরি করে রাখেন, তবে আপনি খুব শান্তভাবে এই সমস্যাটির সমাধান করতে পারবেন।

এর জন্য তিনটি প্রধান কিওয়ার্ড (keywords) রয়েছে:

  • try — "এখানে এমন কিছু কোড আছে, যা কাজ করার সময় হয়তো কোনো সমস্যা (fail) হতে পারে।"
  • catch — "যদি আসলেই কোনো সমস্যা হয়, তবে এরপর কী করতে হবে তা এখানে বলে দেওয়া হলো।"
  • finally — "কোড ঠিকমতো কাজ করুক বা না করুক, সবার শেষে সব সময় এটি অবশ্যই করবে।"

পরিচিতি: try/catch/finally (Basic try/catch/finally)

// Basic try/catch
try
{
Console.Write("Enter a number: ");
string input = "not a number"; // simulating bad input
int number = int.Parse(input); // this will throw!
Console.WriteLine($"You entered: {number}");
}
catch (FormatException ex)
{
Console.WriteLine($"That's not a number! Error: {ex.Message}");
}
catch (OverflowException)
{
Console.WriteLine("That number is too big or too small!");
}
catch (Exception ex) // catches anything else
{
Console.WriteLine($"Something went wrong: {ex.Message}");
}
finally
{
Console.WriteLine("This always runs — cleanup goes here.");
}
// Catching with 'when' filter
try
{
int[] arr = { 1, 2, 3 };
Console.WriteLine(arr[10]); // out of bounds!
}
catch (IndexOutOfRangeException ex) when (ex.Message.Contains("index"))
{
Console.WriteLine("Tried to access an invalid index!");
}
Output
Enter a number: That's not a number! Error: The input string 'not a number' was not in a correct format.
This always runs — cleanup goes here.
Tried to access an invalid index!

সাধারণ কিছু এক্সেপশনের ধরন (Common Exception Types)

সি শার্পে (C#) আগে থেকেই তৈরি করা অনেক ধরনের এক্সেপশন (exception) রয়েছে। নিচে এমন কিছু এক্সেপশনের নাম দেওয়া হলো, যা আপনি সবচেয়ে বেশি দেখতে পাবেন:

  • NullReferenceException — আপনি এমন কিছু ব্যবহার করার চেষ্টা করেছেন, যার মান বা ভ্যালু আসলে null অথবা খালি (এটি সি শার্পের এক নম্বর বাগ বা ত্রুটি!)
  • ArgumentException / ArgumentNullException — কোনো মেথডে ভুল বা খারাপ ইনপুট (input) দেওয়া হয়েছে
  • InvalidOperationException — এই কাজটি করার জন্য অবজেক্টটি এখন সঠিক অবস্থায় (state) নেই
  • IndexOutOfRangeException — অ্যারে (array) বা লিস্টের (list) ইনডেক্স (index) অনেক বড় অথবা নেগেটিভ (negative) বা ঋণাত্মক হয়ে গেছে
  • FormatException — কোনো স্ট্রিংকে পার্স (parse) করে বা পরিবর্তন করে নম্বরে রূপান্তর করা যাচ্ছে না
  • FileNotFoundException — ফাইলটি খুঁজে পাওয়া যাচ্ছে না
  • DivideByZeroException — গণিতের নিয়মে কোনো কিছুকে শূন্য (0) দিয়ে ভাগ করার চেষ্টা করা হয়েছে
  • KeyNotFoundException — ডিকশনারিতে (dictionary) যে কি-টি (key) খোঁজা হচ্ছে, সেটি নেই

সব এক্সেপশনই মূলত System.Exception থেকে ইনহেরিট (inherit) করে হয়ে থাকে। আপনি যখন শুধু Exception ক্যাচ (catch) করেন, তখন এটি সব ধরনের এক্সেপশনকেই ধরে ফেলে — কিন্তু নির্দিষ্ট ধরনের এক্সেপশনগুলোকে ক্যাচ করা অনেক বেশি ভালো, কারণ এতে আপনি ঠিকভাবে বুঝতে পারবেন যে আসলে কোথায় বা কী সমস্যা হয়েছে।

এক্সেপশন থ্রো (Throwing Exceptions) করা এবং কাস্টম এক্সেপশন (Custom Exceptions)

// Throwing exceptions — validate your inputs!
static void SetAge(int age)
{
if (age < 0)
throw new ArgumentException("Age cannot be negative!", nameof(age));
if (age > 150)
throw new ArgumentOutOfRangeException(nameof(age), "Nobody lives that long!");
Console.WriteLine($"Age set to {age}");
}
// Custom exception class
class InsufficientFundsException : Exception
{
public decimal Amount { get; }
public decimal Balance { get; }
public InsufficientFundsException(decimal amount, decimal balance)
: base($"Cannot withdraw {amount:C} — only {balance:C} available.")
{
Amount = amount;
Balance = balance;
}
}
static void Withdraw(decimal balance, decimal amount)
{
if (amount > balance)
throw new InsufficientFundsException(amount, balance);
Console.WriteLine($"Withdrew {amount:C}. Remaining: {balance - amount:C}");
}
// Using them
try { SetAge(25); } catch (Exception ex) { Console.WriteLine(ex.Message); }
try { SetAge(-5); } catch (Exception ex) { Console.WriteLine(ex.Message); }
try
{
Withdraw(100m, 250m);
}
catch (InsufficientFundsException ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine($" Tried: {ex.Amount:C}, Had: {ex.Balance:C}");
}
Output
Age set to 25
Age cannot be negative! (Parameter 'age')
Cannot withdraw $250.00 — only $100.00 available.
  Tried: $250.00, Had: $100.00

রিয়েল ওয়ার্ল্ড বা দৈনন্দিন জীবনের প্যাটার্ন: TryParse বনাম Parse (Real-World Pattern: TryParse vs Parse)

// Instead of try/catch for parsing, use TryParse!
string[] inputs = { "42", "hello", "3.14", "99" };
foreach (string input in inputs)
{
// TryParse — no exceptions, just true/false
if (int.TryParse(input, out int result))
{
Console.WriteLine($" '{input}' → {result} (valid!)");
}
else
{
Console.WriteLine($" '{input}' → not a valid integer");
}
}
// Guard clauses — throw early, avoid deep nesting
static string ProcessOrder(string? customerId, int quantity)
{
// Validate first, fail fast
if (customerId is null)
throw new ArgumentNullException(nameof(customerId));
if (string.IsNullOrWhiteSpace(customerId))
throw new ArgumentException("Customer ID can't be empty", nameof(customerId));
if (quantity <= 0)
throw new ArgumentOutOfRangeException(nameof(quantity), "Must order at least 1");
// Happy path — no deep nesting!
return $"Order placed: {quantity} items for customer {customerId}";
}
try
{
Console.WriteLine(ProcessOrder("C-123", 5));
Console.WriteLine(ProcessOrder(null, 3));
}
catch (ArgumentNullException ex)
{
Console.WriteLine($"Missing: {ex.ParamName}");
}
Output
  '42' → 42 (valid!)
  'hello' → not a valid integer
  '3.14' → not a valid integer
  '99' → 99 (valid!)
Order placed: 5 items for customer C-123
Missing: customerId
Note: 🎯 প্রোগ্রামের স্বাভাবিক প্রবাহ বা ফ্লো কন্ট্রোল (flow control) করতে এক্সেপশন ব্যবহার করবেন না! এক্সেপশন ব্যবহার করা হয় কেবলমাত্র অপ্রত্যাশিত পরিস্থিতিগুলোর জন্য — এমন কোনো পরিস্থিতির জন্য নয় যা আপনি আগে থেকেই অনুমান করতে পারেন বা জানেন। যদি কোনো ব্যবহারকারী (user) একটি ভুল নম্বর লিখতে পারে বলে আপনি মনে করেন, তবে Parse এর সাথে try/catch ব্যবহার না করে TryParse (যা একটি bool রিটার্ন করে) ব্যবহার করুন। এক্সেপশনগুলো বেশ ধীরে কাজ করে (slow); অন্যদিকে bool-এর চেকিং অনেক দ্রুত (fast) হয়ে থাকে।
চ্যালেঞ্জ

ছোট কুইজ

'finally' ব্লকটি ঠিক কখন রান করে বা কাজ করে?

পড়া চালিয়ে যান

LINQFile I/O