Skip to content

C# - Anonymous Methods

C# - From Anonymous Methods to Lambda Expressions

Section titled “C# - From Anonymous Methods to Lambda Expressions”

In C#, a delegate is an object that references a method. Anonymous methods and their modern successor, lambda expressions, provide a powerful way to create inline, unnamed methods that can be passed as delegate parameters. This is especially useful for event handlers, LINQ queries, and short-lived helper functions.

The Evolution: From Named to Inline Methods

Section titled “The Evolution: From Named to Inline Methods”

Let’s see the progression of how you would pass logic to a delegate, starting from a traditional named method and evolving to the concise syntax of a lambda expression.

We’ll use the built-in Action<T> delegate, which represents a method that takes a parameter and returns no value (void).

The original way to use a delegate was to assign it a separate, named method that matched its signature.

using System;
// 1. Define a named method
void PrintMessage(string message)
{
Console.WriteLine($"Named Method: {message}");
}
// 2. Create a delegate instance pointing to the method
Action<string> printer = PrintMessage;
// 3. Call the delegate
printer("Hello, World!");

Introduced in C# 2.0, anonymous methods allowed you to define a method body directly where the delegate instance was created, using the delegate keyword. This avoided the need to create a separate, single-use named method.

Note: While functionally valid, this syntax is now considered historical. Lambda expressions are the preferred modern approach for inline methods.

using System;
// Create a delegate instance using an anonymous method
Action<string> anonymousPrinter = delegate(string msg) {
Console.WriteLine($"Anonymous Method: {msg}");
};
// Call the delegate
anonymousPrinter("This is an anonymous method.");

The code block delegate(string msg) { ... } is the anonymous method. The compiler infers the return type from the return statements inside (or the lack thereof for void).

Lambda expressions, introduced in C# 3.0, provide a much more concise and powerful syntax for creating anonymous functions. They are the cornerstone of modern C# features like LINQ.

A lambda expression is defined with the => (goes to) operator, which separates the input parameters on the left from the expression or statement block on the right.

using System;
// Lambda expression with a statement block
Action<string> lambdaPrinter = (message) =>
{
Console.WriteLine($"Lambda Expression: {message}");
};
lambdaPrinter("Hello from a lambda!");
// For a single expression, the syntax can be even more concise (expression-bodied lambda)
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine($"Sum: {add(5, 10)}"); // Output: Sum: 15
  • Conciseness: The syntax is minimal and highly readable.
  • Type Inference: The compiler can often infer the types of the parameters, so you can write (a, b) => a + b instead of (int a, int b) => a + b.
  • Powering LINQ: Lambdas are the language feature that makes LINQ’s fluent query syntax possible.

Practical Example: Using Lambdas with LINQ

Section titled “Practical Example: Using Lambdas with LINQ”

The most common use case for lambda expressions today is with LINQ to query collections. The lambda defines the logic for filtering, projecting, or ordering data.

using System;
using System.Collections.Generic;
using System.Linq;
var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Use a lambda expression with the 'Where' method to find all even numbers
var evenNumbers = numbers.Where(n => n % 2 == 0);
Console.WriteLine("Even numbers:");
foreach (var num in evenNumbers)
{
Console.Write($"{num} "); // Output: 2 4 6 8 10
}
// Use a lambda with 'Select' to project each number into its square
var squares = numbers.Select(n => n * n);
Console.WriteLine("\n\nSquares:");
foreach (var sq in squares)
{
Console.Write($"{sq} "); // Output: 1 4 9 16 25 36 49 64 81 100
}
  • Prefer Lambda Expressions: For any new C# code, you should always use lambda expressions (=>) instead of the older anonymous method syntax (delegate { ... }).
  • Use Built-in Delegates: Favor the generic Action, Func, and Predicate delegates over defining your own custom delegate types for common patterns.
  • Keep Lambdas Simple: A lambda should represent a small, focused piece of logic. If it becomes long or complex, refactor it into a private named method for better readability and testability.
  • Understand Closures: Be aware that lambdas can “capture” variables from their enclosing scope. This is a powerful feature called a closure, but it can have performance implications or lead to unexpected behavior if not understood properly.