C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
107: The Observer Pattern with Events
I've spent years reviewing code for junior and mid-level devs, and I consistently see the same stumbling block when it comes to the Observer pattern. Most people approach it as a "Design Pattern" they have to implement from scratch—meaning they spend an hour writing custom IObserver and IObservable interfaces, managing internal lists of subscribers, and writing manual foreach loops to notify every object in that list.
Thinking You Need Manual Interface Boilerplate for Every Observer
When you first read the Gang of Four definition of the Observer pattern, it feels like you need a rigid architecture. You might write something like this:
// The "Textbook" way (Too much work in C#)
public interface IPriceObserver {
void Update(decimal price);
}
public class StockTicker {
private List<IPriceObserver> _observers = new();
public void Attach(IPriceObserver observer) => _observers.Add(observer);
public void Notify(decimal price) {
foreach(var obs in _observers) obs.Update(price);
}
}
This works, but it's clunky. You're forcing every "observer" class to implement a specific interface, which creates tight coupling. If you want to observe a StockTicker and a WeatherStation in the same class, you're suddenly implementing five different interfaces. It's a maintenance nightmare.
Leveraging C# Events as a Built-in Observer Implementation
Here is the secret: C# has the Observer pattern baked directly into the language via delegates and events. An event is essentially a managed list of function pointers. When you trigger an event, the CLR handles the "loop and notify" logic for you. You don't need interfaces; you just need a method signature that matches.
Let's look at how we'd handle a stock price update the "C# way." We'll use EventHandler<T>, which is the standard pattern for this.
public class PriceChangedEventArgs : EventArgs {
public decimal NewPrice { get; }
public PriceChangedEventArgs(decimal price) => NewPrice = price;
}
public class StockTicker {
// This is our "Subject" in Observer terms
public event EventHandler<PriceChangedEventArgs> PriceChanged;
public void UpdatePrice(decimal price) {
// The ?. ensures we don't crash if there are no subscribers
PriceChanged?.Invoke(this, new PriceChangedEventArgs(price));
}
}
Now, any class can "observe" this ticker without implementing a single interface. They just subscribe using the += operator. I love this approach because the StockTicker doesn't need to know anything about who is listening—it just shouts into the void, and whoever cares responds.
public class TradingBot {
public void OnPriceChanged(object sender, PriceChangedEventArgs e) {
if (e.NewPrice < 150.00m) {
Console.WriteLine("Buying the dip!");
}
}
}
// Wiring it up
var ticker = new StockTicker();
var bot = new TradingBot();
ticker.PriceChanged += bot.OnPriceChanged; // Subscription
ticker.UpdatePrice(145.00m); // Triggers the bot
One professional tip: Always remember that events create a strong reference from the publisher to the subscriber. If your StockTicker lives for the entire duration of the app, but your TradingBot is meant to be temporary, the bot will never be garbage collected because the ticker is still holding onto it. Get in the habit of unsubscribing with -= when the observer is no longer needed. It's a small detail that separates the seniors from the juniors.
📋 Practical Task
Implementing a Smart Home Environmental Alert System
Your task is to build a small system where a TemperatureSensor notifies different parts of a house when the temperature changes. Instead of the sensor knowing about the hardware it controls, it should use the Observer pattern (via events) to broadcast changes.
- Create a
TemperatureChangedEventArgsclass that inherits fromEventArgsand holds adouble CurrentTempproperty. - Create a
TemperatureSensorclass that has anevent EventHandler<TemperatureChangedEventArgs> TempChanged. Include a methodSetTemperature(double temp)that triggers the event. - Create two separate observer classes:
AirConditioner: Should print "AC Turning On" if the temperature exceeds 25.0°C.HomeLogger: Should print "Log: Temperature updated to [temp]" regardless of the value.
- In a Main method, instantiate the sensor and both observers, subscribe them to the event, and simulate a temperature rise from 20.0°C to 30.0°C to verify both observers react correctly.
There are no comments for now.