CSC367 NET Centric Computing

NET Centric ComputingUnit 1213 min read

Advanced C: Delegates, Events, Generics & Indexers

Unit 12 of NET Centric Computing covers delegates (type-safe function pointers), events (publisher-subscriber pattern), generics (compile-time type safety), and indexers (array-like access). Learn their syntax, real-world use cases (e.g., WhatsApp message callbacks, Ncell billing systems), and how they integrate with A

TAKEAWAYS

  • Delegates enable type-safe function references (like callbacks) and are the backbone of events in C#.
  • Events implement the observer pattern (e.g., WhatsApp notifications) using +/– operators for subscription.
  • Generics provide compile-time type safety (e.g., List<T>) and avoid boxing/unboxing overhead.
  • Indexers allow custom array-like access (e.g., Dictionary<K,V>) via this[] syntax.
  • Lambda expressions (=>) simplify delegate creation (e.g., LINQ queries in NEPSE stock data).
  • Partial classes split code across files (e.g., ASP.NET Core controllers), while sealed classes prevent inheritance.

1. Delegates: The Bridge Between Methods and Callbacks

Delegates are type-safe function pointers that store references to methods. They enable:

  • Event handling (e.g., button clicks in Windows Forms).
  • Callback mechanisms (e.g., async operations in Task).
  • Passing methods as arguments (e.g., LINQ’s Where predicate).

How Delegates Work

A delegate defines a method signature (return type + parameters). You can:

  1. Declare a delegate type.
  2. Instantiate it with a method.
  3. Invoke it like a function.
// Step 1: Declare a delegate
public delegate int MathOperation(int a, int b);

// Step 2: Define methods matching the signature
int Add(int x, int y) => x + y;
int Multiply(int x, int y) => x * y;

// Step 3: Instantiate and invoke
MathOperation op = Add;
Console.WriteLine(op(5, 3)); // Output: 8
op = Multiply;
Console.WriteLine(op(5, 3)); // Output: 15

Real-World Example: WhatsApp Message Callbacks

WhatsApp uses delegates internally to route incoming messages to different handlers (e.g., notifications, media processing). When you receive a message:

  1. The system subscribes a delegate to the OnMessageReceived event.
  2. The delegate invokes the appropriate handler (e.g., ShowNotification() or SaveToCloud()).
flowchart TD
    A["WhatsApp Server"] -->|"Triggers"| B["OnMessageReceived Event"]
    B --> C["Delegate Instance"]
    C --> D["ShowNotification()"]
    C --> E["SaveToCloud()"]
    C --> F["PlaySound()"]

Built-in Delegates

C# provides pre-defined delegates for common scenarios:

Delegate Signature Use Case
Action void Action<T1, T2>(T1, T2) Fire-and-forget operations
Func<T> TResult Func<T1, T2>(T1, T2) Methods returning a value
Predicate<T> bool Predicate<T>(T) Conditions (e.g., LINQ Where)

Example: Using Action for Logging

Action<string> logger = message => Console.WriteLine($"LOG: {message}");
logger("User logged in"); // Output: LOG: User logged in

2. Events: The Observer Pattern in Action

Events extend delegates to enable publisher-subscriber communication. Key features:

  • Encapsulation: Publishers expose events but control invocation.
  • Thread safety: Events are thread-safe by default.
  • Weak references: Prevent memory leaks (via WeakEventManager).
Event DeclarationDefine `eventEventHandler<TemperatuSubscriptionSubscriber calls`+=` to attach handlerEvent RaisingPublisher invokes`TemperatureChanged?.IHandler ExecutionAll subscribersexecute (e.g., send SM
Lifecycle of an event in C# (Ncell billing alert system)

Syntax

public class TemperatureSensor
{
    // Step 1: Declare an event using a delegate
    public event EventHandler<TemperatureEventArgs> TemperatureChanged;

    // Step 2: Raise the event (invokes all subscribers)
    protected virtual void OnTemperatureChanged(TemperatureEventArgs e)
    {
        TemperatureChanged?.Invoke(this, e);
    }
}

// Custom event args (must inherit EventArgs)
public class TemperatureEventArgs : EventArgs
{
    public double Celsius { get; }
    public TemperatureEventArgs(double temp) => Celsius = temp;
}

Real-World Example: Ncell Billing Alerts

Ncell’s billing system uses events to notify users when their balance is low:

  1. The UserAccount class publishes a BalanceLow event.
  2. Subscribers (e.g., SMSService, EmailService) react when the event fires.
sequenceDiagram
    UserAccount->>SMSService: Subscribe(BalanceLow)
    UserAccount->>EmailService: Subscribe(BalanceLow)
    UserAccount->>UserAccount: CheckBalance() (balance < 100)
    UserAccount->>SMSService: BalanceLow(95)
    UserAccount->>EmailService: BalanceLow(95)
    SMSService->>User: Send SMS: "Low balance!"
    EmailService->>User: Send Email: "Top-up now!"

Event Best Practices

  • Use protected virtual for raising events (allows derived classes to override).
  • Null-check with ?.Invoke to avoid NullReferenceException.
  • Avoid exposing delegates directly (use add/remove syntax for events).

Example: Custom Exception Event

public class InvalidSubjectException : Exception
{
    public event EventHandler<SubjectEventArgs> SubjectInvalid;

    public InvalidSubjectException(string subject)
    {
        if (subject != "C#")
            SubjectInvalid?.Invoke(this, new SubjectEventArgs(subject));
    }
}

public class SubjectEventArgs : EventArgs
{
    public string InvalidSubject { get; }
    public SubjectEventArgs(string subject) => InvalidSubject = subject;
}

3. Generics: Type-Safe Reusable Code

Generics allow compile-time type checking for classes, methods, and collections. Benefits:

  • Performance: Avoids boxing/unboxing (e.g., List<int> vs. ArrayList).
  • Type safety: Compile-time errors for invalid types.
  • Code reuse: Single implementation for multiple types (e.g., Dictionary<K,V>).
ProcessOrder(Book)ValidateBookStock()BookOrder (T: `Book`)ProcessOrder(Electronics)CheckWarranty()ElectronicsOrder (T: `Electronics`)Generic `OrderProcessor<T>`
Daraz’s generic `OrderProcessor<T>` handling multiple order types with shared logic

Generic Classes and Methods

// Generic class
public class Box<T>
{
    public T Content { get; set; }
}

// Generic method
public T Max<T>(T a, T b) where T : IComparable<T>
{
    return a.CompareTo(b) > 0 ? a : b;
}

Real-World Example: Daraz Order Processing

Daraz uses generics to process orders of any type (books, electronics, etc.) with a single OrderProcessor<T> class:

public class OrderProcessor<T> where T : IOrder
{
    public void ProcessOrder(T order)
    {
        Console.WriteLine($"Processing {typeof(T).Name}: {order.Id}");
        // Common logic for all order types
    }
}

// Example usage:
var bookOrder = new BookOrder { Id = 101 };
var electronicsOrder = new ElectronicsOrder { Id = 201 };
OrderProcessor<IOrder> processor = new OrderProcessor<IOrder>();
processor.ProcessOrder(bookOrder);      // Output: Processing BookOrder: 101
processor.ProcessOrder(electronicsOrder); // Output: Processing ElectronicsOrder: 201

Constraints on Generic Types

Constraint Example Purpose
where T : struct Stack<T> Value types only (no null)
where T : class List<T> Reference types only (allows null)
where T : new() T item = new T(); Type must have a parameterless constructor
where T : IComparable<T> Max<T>(a, b) Type must implement IComparable<T>

Example: Generic Exception Handler

public class CustomException<T> : Exception where T : Exception
{
    public T InnerException { get; }
    public CustomException(T ex) => InnerException = ex;
}

// Usage:
try { /* Risky code */ }
catch (SqlException ex) { throw new CustomException<SqlException>(ex); }

4. Indexers: Custom Array Access

Indexers allow array-like access to objects using this[] syntax. Use cases:

  • Collections: List<T>, Dictionary<K,V>.
  • Custom data structures: E.g., a Matrix class with 2D access.

Syntax

public class MyList<T>
{
    private T[] _items = new T[100];
    private int _count;

    // Indexer declaration
    public T this[int index]
    {
        get { return _items[index]; }
        set { _items[index] = value; }
    }

    public int Count => _count;
}

Real-World Example: Kathmandu Traffic Routes

Imagine a TrafficSystem class that uses indexers to access routes by ID:

public class TrafficSystem
{
    private string[] _routes = new string[1000];

    public string this[int routeId]
    {
        get => _routes[routeId];
        set => _routes[routeId] = value;
    }
}

// Usage:
var traffic = new TrafficSystem();
traffic[42] = "Ring Road to Thamel"; // Set route
Console.WriteLine(traffic[42]);       // Output: Ring Road to Thamel

Indexers vs. Properties

Feature Indexer Property
Syntax this[args] PropertyName
Purpose Array-like access Named access
Parameters Can have multiple parameters None
Example list[0] = "Hello" person.Name = "Alice"

Example: Read-Only Indexer

public class ReadOnlyCollection<T>
{
    private T[] _data;
    public ReadOnlyCollection(T[] data) => _data = data;

    public T this[int index] => _data[index]; // Only getter
}

5. Lambda Expressions: Concise Delegates

Lambda expressions (=>) provide a shorter syntax for delegates, especially useful with LINQ.

Syntax

// Lambda with no parameters
Action greet = () => Console.WriteLine("Hello!");
greet();

// Lambda with parameters
Func<int, int, int> add = (x, y) => x + y;
Console.WriteLine(add(5, 3)); // Output: 8

Real-World Example: NEPSE Stock Filter

NEPSE’s website filters stocks using lambda expressions to select high-value shares:

var stocks = new List<Stock>
{
    new Stock { Name = "NMB", Price = 300 },
    new Stock { Name = "NTC", Price = 150 },
    new Stock { Name = "Ncell", Price = 450 }
};

// Filter stocks priced > 300 using lambda
var expensiveStocks = stocks.Where(s => s.Price > 300);
foreach (var stock in expensiveStocks)
    Console.WriteLine(stock.Name); // Output: Ncell

Lambda vs. Anonymous Methods

Feature Lambda (=>) Anonymous Method (delegate)
Syntax (args) => expression delegate { ... }
Readability More concise Verbose
Use Case LINQ, short callbacks Complex logic

Example: Lambda for Event Handler

button.Click += (sender, e) => MessageBox.Show("Clicked!");

6. Partial Classes and Sealed Classes

Partial Classes: Split Code Across Files

Used in ASP.NET Core to separate:

  • Designers (e.g., .Designer.cs for WinForms).
  • Logic (e.g., Controller.cs for MVC).
// File 1: HomeController.Designer.cs
public partial class HomeController { /* Auto-generated */ }

// File 2: HomeController.cs
public partial class HomeController : Controller
{
    public IActionResult Index() => View();
}

Sealed Classes: Prevent Inheritance

  • Performance: Slightly faster (no virtual method overhead).
  • Security: Prevent derived classes (e.g., string is sealed).
public sealed class ImmutablePoint
{
    public int X { get; }
    public int Y { get; }
    public ImmutablePoint(int x, int y) => (X, Y) = (x, y);
}

Exam Tip

  1. Delegates/Events:

    • Always declare delegates before use.
    • Use EventHandler<T> for custom events with data.
    • Common pitfall: Forgetting ?.Invoke (causes NullReferenceException).
  2. Generics:

    • Constraints (where T :) are critical for type safety.
    • Performance: Prefer List<T> over ArrayList (no boxing).
  3. Indexers:

    • Signature: Must match this[args] (e.g., this[int index]).
    • Comparison: Indexers are to arrays as properties are to objects.
  4. Lambda Expressions:

    • LINQ: Where, Select, OrderBy use lambdas.
    • Shortcut: x => x.ToString() is equivalent to delegate { return x.ToString(); }.
  5. Partial/Sealed:

    • Partial: Used in ASP.NET Core (e.g., Controller classes).
    • Sealed: Prevents inheritance (e.g., string, System.ValueType).

In the real world

  • WhatsApp (Meta): Uses delegates to route incoming messages to different handlers (e.g., OnMessageReceived event triggers notifications, media processing, or cloud backups).
  • Ncell Billing System: Implements the observer pattern via events (BalanceLow) to notify users via SMS/email when their balance drops below NPR 100.
  • Daraz Order Processing: Leverages generics (OrderProcessor<T>) to handle orders for books, electronics, or groceries with a single codebase, avoiding type-specific logic duplication.

Based on the TU BSc CSIT syllabus for NET Centric Computing (CSC367), unit 12.

Discussion

Loading…