Lesson ১৫৮ মিনিট পড়া

ডেলিগেটস, ইভেন্টস এবং ল্যামবডাস (Delegates, Events & Lambdas)

অন্যান্য ডেটার মতো আচার-আচরণ বা বিহেভিয়ারও (behavior) আদান-প্রদান করুন — আর এটাই হলো আধুনিক সি শার্পের আসল গোপন রহস্য

ডেলিগেট কী? (What Is a Delegate?)

সাধারণত, ভেরিয়েবলগুলো (variables) নিজেদের ভেতরে ডেটা (data) — যেমন সংখ্যা, স্ট্রিং (strings) বা অবজেক্ট (objects) ধরে রাখে। কিন্তু কেমন হতো যদি আপনি একটি ভেরিয়েবলের ভেতরে কোনো মেথডকে (method) সংরক্ষণ করতে পারতেন? মূলত একটি ডেলিগেট (delegate) ঠিক এই কাজটিই করে থাকে।

একটি ডেলিগেটকে একটি টিভির রিমোট কন্ট্রোলের (TV remote control) মতো ভাবতে পারেন। রিমোটটি জানে না যে তাকে কোন টিভির দিকে তাক করানো হয়েছে — সে শুধু এটুকু জানে যে, "আমি যদি বোতাম চাপি, তবে কিছু একটা ঠিকই ঘটবে।" আপনি চাইলে রিমোটটিকে বিভিন্ন টিভির (অর্থাৎ বিভিন্ন মেথডের) দিকে তাক করতে পারেন এবং বোতাম চাপলেই ওই মুহূর্তে রিমোটের সাথে যেটি সংযোগ করা আছে, সেটি চালু হয়ে যাবে।

এটি খুবই শক্তিশালী একটি জিনিস, কারণ এর ফলে আপনি অনেক নমনীয় বা ফ্লেক্সিবল (flexible) কোড লিখতে পারবেন। কোন মেথডটিকে কল করতে হবে তা আগে থেকেই হার্ড-কোড (hard-code) করে না লিখে, আপনি এখন প্যারামিটার হিসেবে পুরো মেথডটিকেই পাঠিয়ে দিতে পারবেন। এর ফলে যাকে কল করা হবে, সে নিজেই ঠিক করবে যে এরপর আসলে কী ঘটবে।

ডেলিগেটস — ভেরিয়েবলের রূপ নেওয়া মেথডস (Delegates — Methods as Variables)

// Define a delegate type (the "shape" of methods it can hold)
delegate int MathOperation(int a, int b);
// Methods that match the delegate's shape
static int Add(int a, int b) => a + b;
static int Multiply(int a, int b) => a * b;
static int Max(int a, int b) => a > b ? a : b;
// Use the delegate like a variable
MathOperation op = Add;
Console.WriteLine($"Add: {op(10, 5)}");
op = Multiply; // point at a different method
Console.WriteLine($"Multiply: {op(10, 5)}");
op = Max;
Console.WriteLine($"Max: {op(10, 5)}");
// Pass a delegate to another method
static int ApplyToArray(int[] numbers, MathOperation operation)
{
int result = numbers[0];
for (int i = 1; i < numbers.Length; i++)
result = operation(result, numbers[i]);
return result;
}
int[] nums = { 3, 7, 2, 9, 4 };
Console.WriteLine($"\nSum of all: {ApplyToArray(nums, Add)}");
Console.WriteLine($"Product: {ApplyToArray(nums, Multiply)}");
Console.WriteLine($"Largest: {ApplyToArray(nums, Max)}");
Output
Add: 15
Multiply: 50
Max: 10

Sum of all: 25
Product: 1512
Largest: 9

Action<T> এবং Func<T,TResult> — সি শার্পের বিল্ট-ইন ডেলিগেট (Action<T> and Func<T,TResult> — Built-in Delegates)

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

  • Action<T> — এটি এমন একটি মেথড, যা প্যারামিটার গ্রহণ করে ঠিকই, কিন্তু এর বদলে কোনো কিছুই (void) রিটার্ন করে না। Action<string> একটি স্ট্রিং গ্রহণ করে, কিন্তু এটি শুধু void রিটার্ন করে।
  • Func<T, TResult> — এটি এমন একটি মেথড, যা প্যারামিটার গ্রহণ করে এবং এর বদলে একটি ভ্যালু (value) রিটার্ন করে। এখানকার সবচেয়ে শেষের টাইপটি (type) সব সময়ই মেথডটির রিটার্ন টাইপ হিসেবে ধরা হয়। Func<int, int, int> মূলত দুটি int গ্রহণ করে এবং একটি int রিটার্ন করে।

এখন আর আপনাকে নিজের মতো করে কোনো ডেলিগেট টাইপ লিখতে হবে না বললেই চলে — কারণ Action এবং Func দিয়েই এখন ৯৯% কাজ করে ফেলা যায়।

Action, Func এবং ল্যামবডা এক্সপ্রেশন (Action, Func & Lambda Expressions)

// Action — takes input, returns nothing
Action<string> shout = message => Console.WriteLine(message.ToUpper() + "!!!");
shout("hello");
shout("watch out");
// Func — takes input, returns output
Func<int, int, int> add = (a, b) => a + b;
Func<string, int> wordCount = text => text.Split(' ').Length;
Func<double, double> toCelsius = f => (f - 32) * 5.0 / 9.0;
Console.WriteLine($"3 + 4 = {add(3, 4)}");
Console.WriteLine($"Words: {wordCount("The quick brown fox")}");
Console.WriteLine($"72°F = {toCelsius(72):F1}°C");
// Lambda with multiple statements (use { })
Func<int, string> classify = n =>
{
if (n > 0) return "positive";
if (n < 0) return "negative";
return "zero";
};
Console.WriteLine($"5 is {classify(5)}");
Console.WriteLine($"-3 is {classify(-3)}");
Console.WriteLine($"0 is {classify(0)}");
// Passing lambdas directly (no variable needed)
var numbers = new List<int> { 5, 12, 8, 3, 17, 9 };
var big = numbers.Where(n => n > 10).ToList();
Console.WriteLine($"\nBigger than 10: [{string.Join(", ", big)}]");
Output
HELLO!!!
WATCH OUT!!!
3 + 4 = 7
Words: 4
72°F = 22.2°C
5 is positive
-3 is negative
0 is zero

Bigger than 10: [12, 17]

ইভেন্টস বা ইভেন্ট — দ্য নোটিফিকেশন সিস্টেম (Events — The Notification System)

একটি ইভেন্ট (event) হলো নিউজলেটার সাবস্ক্রিপশনের (newsletter subscription) মতো। ধরা যাক, একজন প্রকাশক বা পাবলিশার (publisher, যেমন- একটি বাটন বা Button) বলছে, "আমার একটি Click বা ক্লিক ইভেন্ট আছে।" এখন যেকোনো সংখ্যক সাবস্ক্রাইবার বা গ্রাহক (subscribers) চাইলে += ব্যবহার করে ওই ইভেন্টে সাইন আপ করতে পারে। এরপর যখনই ইভেন্টটি ঘটবে বা ট্রিগার হবে, তখনই সব গ্রাহককে নোটিফিকেশন (notification) পাঠিয়ে তা জানিয়ে দেওয়া হবে।

ইভেন্টগুলো মূলত পেছনের দিক দিয়ে ডেলিগেট (delegates) হিসেবেই কাজ করে, কিন্তু এটি একটি নতুন নিয়ম বা বাধ্যবাধকতাও যোগ করে: গ্রাহকরা (subscribers) কেবল += দিয়ে যুক্ত হতে (subscribe) বা -= দিয়ে বাদ পড়তে (unsubscribe) পারবে। তারা চাইলে নিজেরা ইভেন্টটিকে ফায়ার (fire) করতে বা চালু করতে পারবে না, এমনকি অন্য গ্রাহকদের জায়গাও নিতে পারবে না। এর ফলে জিনিসগুলো অনেক নিরাপদ থাকে।

ইভেন্ট বা ইভেন্টস হলো ইউআই প্রোগ্রামিং (UI programming, যেমন- বাটনে ক্লিক করা, ফর্ম সাবমিট করা) এবং অন্যান্য অনেক ডিজাইনের (যেমন- observer pattern, pub/sub) একদম মেরুদণ্ড (backbone)।

ইভেন্টস — সাবস্ক্রাইব করা, পাবলিশ করা এবং সে অনুযায়ী কাজ করা (Events — Subscribe, Publish, React)

class TemperatureSensor
{
// Event that fires when temperature changes
public event Action<double>? TemperatureChanged;
public event Action<double>? OverheatWarning;
private double _temp;
public void SetTemperature(double newTemp)
{
_temp = newTemp;
Console.WriteLine($"Sensor: Temperature is now {_temp}°C");
// Fire the event — notify all subscribers
TemperatureChanged?.Invoke(_temp);
if (_temp > 100)
OverheatWarning?.Invoke(_temp);
}
}
// Create sensor
var sensor = new TemperatureSensor();
// Subscribe to events with lambdas
sensor.TemperatureChanged += temp =>
Console.WriteLine($" Logger: Recorded {temp}°C");
sensor.TemperatureChanged += temp =>
Console.WriteLine($" Display: Showing {temp}°C on screen");
sensor.OverheatWarning += temp =>
Console.WriteLine($" ALARM: DANGER! {temp}°C is too hot!");
// Simulate temperature changes
sensor.SetTemperature(22.5);
Console.WriteLine();
sensor.SetTemperature(75.0);
Console.WriteLine();
sensor.SetTemperature(105.3);
Output
Sensor: Temperature is now 22.5°C
  Logger: Recorded 22.5°C
  Display: Showing 22.5°C on screen

Sensor: Temperature is now 75°C
  Logger: Recorded 75°C
  Display: Showing 75°C on screen

Sensor: Temperature is now 105.3°C
  Logger: Recorded 105.3°C
  Display: Showing 105.3°C on screen
  ALARM: DANGER! 105.3°C is too hot!

রিয়েল-ওয়ার্ল্ড: কলব্যাকস এবং হায়ার-অর্ডার মেথডস (Real-World: Callbacks & Higher-Order Methods)

// Higher-order method: takes a function as a parameter
static List<T> Filter<T>(List<T> items, Func<T, bool> predicate)
{
var result = new List<T>();
foreach (var item in items)
if (predicate(item))
result.Add(item);
return result;
}
static List<TOut> Map<TIn, TOut>(List<TIn> items, Func<TIn, TOut> transform)
{
var result = new List<TOut>();
foreach (var item in items)
result.Add(transform(item));
return result;
}
// Use with different lambdas — same method, different behavior!
var names = new List<string> { "Anika", "Rafi", "Sadia", "Tariq", "Esha" };
var longNames = Filter(names, n => n.Length > 3);
var shouted = Map(names, n => n.ToUpper());
var lengths = Map(names, n => n.Length);
Console.WriteLine($"Long names: [{string.Join(", ", longNames)}]");
Console.WriteLine($"Shouted: [{string.Join(", ", shouted)}]");
Console.WriteLine($"Lengths: [{string.Join(", ", lengths)}]");
// Callback pattern: "call me when you're done"
static void DownloadData(string url, Action<string> onComplete)
{
// Simulate download
string data = $"Data from {url}";
Console.WriteLine($"Downloading {url}...");
onComplete(data); // call the callback!
}
DownloadData("api.example.com/users", result =>
{
Console.WriteLine($"Got: {result}");
});
Output
Long names: [Anika, Sadia, Tariq]
Shouted:    [ALICE, BOB, CHARLIE, DIANA, EVE]
Lengths:    [5, 3, 7, 5, 3]
Downloading api.example.com/users...
Got: Data from api.example.com/users
Note: 🔑 ল্যামবডার চিট শিট (Lambda cheat sheet): (x) => x * 2 মানে হলো "একটি x নাও এবং x এর দ্বিগুণ (times 2) রিটার্ন করো।" এই => অ্যারো বা তীরচিহ্নটিকে "গোজ টু (goes to)" অথবা "বিকামস (becomes)" বলে পড়া হয়। যদি কোনো প্যারামিটার না থাকে? তবে () => doSomething() ব্যবহার করুন। যদি অনেকগুলো প্যারামিটার থাকে? তবে (a, b) => a + b ব্যবহার করুন। আর যদি অনেকগুলো স্টেটমেন্ট (statements) থাকে? তবে (x) => { ...; return result; } ব্যবহার করুন।
চ্যালেঞ্জ

ছোট কুইজ

Action এবং Func এর মধ্যে মূল পার্থক্য কী?
File I/O