Lambda Expressions
Vaibhav • September 11, 2025
In the previous article, we explored anonymous methods - a way to define inline logic without declaring a separate method. Lambda expressions take that idea further, offering a more concise and expressive syntax for writing inline functions. They are a cornerstone of modern C# programming and are used extensively in LINQ, event handling, and functional-style code.
Lambda expressions allow you to write short, readable functions directly in the place where they’re needed. They’re built on top of delegates and anonymous methods, but with cleaner syntax and better type inference. In this article, we’ll explore how lambda expressions work, how they’re declared, and how they’re used in real-world scenarios - all while staying within the concepts introduced so far.
What is a Lambda Expression?
A lambda expression is a shorthand syntax for writing anonymous methods. It uses the =>
operator to separate the parameter list from the method body. The result is
a compact, readable function that can be assigned to a delegate or passed as an argument.
Logger log = (msg) => Console.WriteLine("Log: " + msg);
log("System started"); // Output: Log: System started
This lambda expression takes one parameter msg
and writes it to the console. It
matches the Logger
delegate’s signature, so it can be assigned directly. When
log
is invoked, the lambda runs.
Lambda Syntax Breakdown
The syntax for a lambda expression consists of a parameter list, the =>
operator, and a method body. The body can be a single expression or a block of statements.
// Single-expression lambda
Calculator calc = (a, b) => a + b;
// Block-bodied lambda
Calculator verboseCalc = (a, b) =>
{
Console.WriteLine("Adding " + a + " and " + b);
return a + b;
};
The first lambda returns the sum of a
and b
directly. The second uses a block to include additional logic. Both match the Calculator
delegate’s signature.
Type Inference and Parameter Types
In most cases, you don’t need to specify parameter types in a lambda expression. The compiler infers them from the delegate type. This keeps the syntax clean and readable.
Calculator calc = (x, y) => x * y;
Here, the compiler knows that x
and y
are
integers because Calculator
takes two int
parameters. You can specify types explicitly if needed, but it’s rarely necessary.
Lambda expressions rely on delegate types for type inference. You must assign them to a delegate or use them in a context where the expected type is known.
Using Lambdas with Built-in Delegates
C# includes several built-in delegate types, such as Action
, Func
, and Predicate
. Lambda expressions work
seamlessly with these types, making them ideal for functional-style programming.
Action greet = () => Console.WriteLine("Hello!");
greet(); // Output: Hello!
Func<int, int, int> multiply = (a, b) => a * b;
Console.WriteLine(multiply(3, 4)); // Output: 12
The Action
delegate represents a method with no return value. The Func
delegate represents a method that returns a value. Lambda expressions make
it easy to define these methods inline.
Capturing Variables in Lambdas
Like anonymous methods, lambda expressions can capture variables from the surrounding scope. This allows them to maintain state and customize behavior based on context.
int count = 0;
Action increment = () =>
{
count++;
Console.WriteLine("Count: " + count);
};
increment(); // Output: Count: 1
increment(); // Output: Count: 2
The lambda captures the count
variable and modifies it each time it’s called.
This behavior is essential for building closures and dynamic callbacks.
Captured variables are stored in a hidden class created by the compiler. This allows lambdas to maintain state across invocations, even if the original context is gone.
Using Lambdas in Collections
Lambda expressions are widely used with collections - especially with methods like Find
, Where
, and ForEach
. They allow you to define filtering and transformation logic inline.
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
int even = numbers.Find(n => n % 2 == 0);
Console.WriteLine(even); // Output: 2
The lambda n => n % 2 == 0
defines the condition for finding an even number.
It’s passed directly to Find
, keeping the logic close to the usage.
Using Lambdas in Events
Lambdas are perfect for event handlers - especially when the logic is short and specific to the event. You can
attach them directly to an event using +=
.
public event Logger OnLog;
OnLog += (msg) => Console.WriteLine("Event: " + msg);
OnLog?.Invoke("User logged in"); // Output: Event: User logged in
This pattern is common in UI frameworks and reactive systems. It avoids cluttering your code with separate handler methods.
Use lambdas for short-lived event handlers. For reusable or complex logic, prefer named methods for clarity and maintainability.
Returning Lambdas from Methods
You can return lambda expressions from methods - especially when using Func
or
custom delegate types. This allows you to generate behavior dynamically.
Func<int, int> MakeMultiplier(int factor)
{
return x => x * factor;
}
var triple = MakeMultiplier(3);
Console.WriteLine(triple(5)); // Output: 15
The method MakeMultiplier
returns a lambda that multiplies its input by a given
factor. This pattern is useful for building configurable logic and higher-order functions.
Lambda Expressions vs Anonymous Methods
Lambda expressions and anonymous methods are similar - both define inline functions. However, lambdas offer cleaner syntax, better type inference, and more flexibility. For example, lambdas can be used in expression trees and LINQ queries, while anonymous methods cannot.
Use lambda expressions whenever possible. They’re the modern, idiomatic way to write inline functions in C#.
Common Mistakes with Lambdas
Lambdas are powerful, but they can be misused. Here are some common mistakes to avoid:
- Capturing variables unintentionally - especially in loops
- Using lambdas for complex logic - prefer named methods
- Assuming lambdas are always faster - measure performance
Be deliberate with your use of lambdas. They’re best for short, focused logic that improves readability.
Performance Considerations
Lambda expressions are compiled into regular methods behind the scenes. They’re efficient and optimized by the runtime. However, captured variables may introduce hidden allocations - especially if the lambda is stored long-term or used in tight loops.
If performance is critical, profile your code and consider using named methods or avoiding closures.
Note: Lambdas are safe and efficient for most scenarios. Just be mindful of captured variables and lifetime.
Summary
Lambda expressions are a concise, expressive way to define inline functions in C#. They build on delegates and anonymous methods, offering cleaner syntax and better type inference. You learned how to declare lambdas, use them with built-in delegates, capture variables, and apply them in collections and events.
Lambdas are a key part of functional programming in C#. They make your code more readable, flexible, and powerful. In the next article, we’ll explore Expression Trees - a way to represent lambdas as data structures, enabling dynamic query generation and advanced scenarios.