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
48: Lambda Expressions in C#
You've likely seen the => operator in C# and thought, "Okay, this is just a shortcut for writing a small method." It's a common assumption because, on the surface, that's exactly what it looks like. But here is where the misconception trips people up: they think a lambda expression is a *type* of function you define independently.
If you try to just drop a lambda expression into the middle of a class like this:
public class OrderProcessor
{
// This will not compile!
(int price) => price * 1.10m;
}
The compiler will scream at you. Why? Because a lambda expression isn't a method; it's an anonymous function that is treated as data. It has no name and no place to live until you assign it to a delegate. In my experience, the "aha!" moment happens when you stop thinking of lambdas as "small methods" and start thinking of them as "logic that can be passed around as a variable."
The Delegate is the Bucket
To use a lambda, you need a "bucket" to hold it. In C#, those buckets are usually Func<...> or Action<...>. If your lambda returns a value, use Func. If it just performs a task and returns nothing (void), use Action.
Let's look at a real-world scenario. Imagine you're building a system to validate user passwords. Instead of writing ten different methods for ten different rules, you can treat the rules as data:
// Func<Input, Output>
// This bucket takes a string and returns a bool
Func<string, bool> isLongEnough = (password) => password.Length >= 8;
Func<string, bool> hasNumber = (password) => password.Any(char.IsDigit);
bool isValid = isLongEnough("Secret123") && hasNumber("Secret123");
I love this approach because it makes your code incredibly flexible. You can now put these Func objects into a List<Func<string, bool>> and loop through them, rather than hard-coding a massive chain of if statements.
Passing Logic into LINQ
You'll most frequently encounter lambdas when using LINQ. When you call .Where() or .Select(), you aren't passing a value to the method; you're passing a formula that the method will apply to every item in the collection.
Consider a list of Product objects. If you want to find all products that are both "Electronics" and cost more than $500, you don't write a loop. You pass a lambda that describes the criteria:
var expensiveElectronics = products.Where(p => p.Category == "Electronics" && p.Price > 500);
Read that p => ... as "for each element p, return true if this condition is met." The Where method handles the looping; your lambda provides the intelligence.
Capturing the Surroundings
One of the most powerful (and sometimes dangerous) features of lambdas is their ability to "capture" variables from the scope where they are defined. This is called a closure.
Suppose you have a threshold value that changes based on user settings. You can bake that variable directly into your lambda:
decimal userPriceThreshold = 100.00m;
// The lambda "captures" userPriceThreshold from the outside
var affordableItems = products.Where(p => p.Price < userPriceThreshold);
The lambda doesn't just take the value of the threshold at the moment it was created; it keeps a reference to the variable itself. If userPriceThreshold changed before the Where clause actually executed, the lambda would use the updated value. Just be careful with this in asynchronous code, or you'll find yourself chasing ghosts in your debugger.
📋 Practical Task
Building a Dynamic Discount Engine
You are tasked with creating a flexible discount system for a shopping cart. Instead of hard-coding discounts, you need to create a system that can accept different "discount rules" as lambdas.
Requirements:
- Create a
Productclass withName(string) andPrice(decimal). - Create a
List<Product>containing at least 4 items (e.g., "Laptop" at 1200, "Mouse" at 25, "Keyboard" at 80, "Monitor" at 300). - Define a
Func<Product, decimal>calleddiscountRule. - Assign a lambda to
discountRulethat applies a 10% discount if the product price is over 100, otherwise no discount. - Use a
foreachloop to print each product's name and its final price after applying thediscountRule. - Bonus: Change the
discountRulelambda to a different rule (e.g., a flat $20 off for everything) and verify that the same loop now produces different results without changing the loop logic itself.
There are no comments for now.