Skip to main content

8.4 - Lambda Expressions

Lambda expressions represent one of the most powerful features in modern C#, bringing functional programming concepts into the language. They provide an even more concise way to define inline methods than anonymous methods, and they're essential for working with LINQ and many other advanced C# features.

What Are Lambda Expressions?

A lambda expression is a concise way to represent an anonymous function that can contain expressions and statements. The term "lambda" comes from lambda calculus, a mathematical system for expressing computation based on function abstraction.

In C#, lambda expressions are primarily used to:

  1. Create delegate instances with minimal syntax
  2. Define inline functions for LINQ queries
  3. Create expression trees for dynamic queries
  4. Implement event handlers and callbacks

Basic Syntax

The basic syntax of a lambda expression uses the lambda operator => (sometimes called the "goes to" operator):

// Lambda expression with no parameters
Action sayHello = () => Console.WriteLine("Hello, World!");

// Lambda expression with one parameter
Action<string> greet = (name) => Console.WriteLine($"Hello, {name}!");

// Lambda expression with multiple parameters
Func<int, int, int> add = (a, b) => a + b;

The left side of the => operator specifies the input parameters (if any), and the right side contains the expression or statement block that's the body of the lambda.

Expression Lambdas vs. Statement Lambdas

Lambda expressions come in two forms:

Expression Lambdas

Expression lambdas have a single expression as their body and implicitly return the result of that expression:

// Expression lambda that returns a value
Func<int, int> square = x => x * x;

// Usage
int result = square(5); // result = 25

Statement Lambdas

Statement lambdas have a block of statements enclosed in braces as their body:

// Statement lambda with multiple statements
Action<Player> damageAndNotify = player =>
{
player.Health -= 10;
player.IsInjured = true;
Console.WriteLine($"{player.Name} has been injured!");
};

// Usage
damageAndNotify(currentPlayer);

Statement lambdas must use the return keyword if they need to return a value:

Func<Enemy, float> calculateThreat = enemy =>
{
float baseThreat = enemy.Power * enemy.Aggression;
if (enemy.HasRangedAttack)
baseThreat *= 1.5f;
return baseThreat;
};

Type Inference in Lambda Expressions

C# can often infer the types of lambda parameters based on the context:

// Parameter type is inferred as int
Func<int, bool> isEven = x => x % 2 == 0;

// Multiple parameters with inferred types
Func<int, int, bool> areEqual = (x, y) => x == y;

You can also explicitly specify the types:

Func<int, bool> isEven = (int x) => x % 2 == 0;
Func<int, int, bool> areEqual = (int x, int y) => x == y;

Explicit typing is useful when the compiler can't infer the types or when you want to make the code more readable.

Capturing Variables

Like anonymous methods, lambda expressions can capture variables from the surrounding scope:

public void ConfigureEnemy(Enemy enemy, float difficultyMultiplier)
{
// Captures difficultyMultiplier from the outer scope
enemy.CalculateDamage = (baseDamage) => baseDamage * difficultyMultiplier;
}

The same considerations about variable lifetime and closure behavior apply to lambda expressions as they do to anonymous methods.

Practical Example: Sorting Game Objects

Lambda expressions are particularly useful for custom sorting operations:

public class GameObject
{
public string Name { get; set; }
public float Distance { get; set; }
public int Priority { get; set; }
}

public void SortGameObjects(List<GameObject> objects)
{
// Sort by distance (closest first)
objects.Sort((a, b) => a.Distance.CompareTo(b.Distance));

// Sort by priority (highest first), then by name
objects.Sort((a, b) =>
{
int priorityComparison = b.Priority.CompareTo(a.Priority);
if (priorityComparison != 0)
return priorityComparison;
return a.Name.CompareTo(b.Name);
});
}

Lambda Expressions with Events

Lambda expressions are perfect for simple event handlers:

// Traditional approach
button.onClick.AddListener(OnButtonClick);

private void OnButtonClick()
{
LoadNextLevel();
}

// Lambda approach
button.onClick.AddListener(() => LoadNextLevel());

// Lambda with additional logic
button.onClick.AddListener(() =>
{
SaveGameState();
LoadNextLevel();
UpdateUI();
});

Lambda Expressions with LINQ

One of the most common uses for lambda expressions is with LINQ (Language Integrated Query), which we'll cover in detail in the next section. Here's a preview:

List<Enemy> enemies = GetAllEnemies();

// Find enemies with low health
var weakEnemies = enemies.Where(e => e.Health < 30);

// Get the names of all active enemies
var enemyNames = enemies
.Where(e => e.IsActive)
.Select(e => e.Name);

// Check if any enemy is a boss
bool hasBoss = enemies.Any(e => e.IsBoss);

Asynchronous Programming with Lambdas

Lambda expressions work seamlessly with C#'s asynchronous programming features:

// Start a task that runs a lambda asynchronously
Task.Run(() =>
{
LoadResources();
InitializeAI();
Console.WriteLine("Background initialization complete");
});

// Async lambda (requires C# 7.0 or later)
button.onClick.AddListener(async () =>
{
await SaveGameStateAsync();
await LoadNextLevelAsync();
});

Lambda Expressions vs. Anonymous Methods

Lambda expressions have largely replaced anonymous methods in modern C# code due to their more concise syntax and additional capabilities:

// Anonymous method
enemies.RemoveAll(delegate(Enemy e) { return e.Health <= 0; });

// Equivalent lambda expression
enemies.RemoveAll(e => e.Health <= 0);

The lambda syntax is shorter and often more readable, especially for simple operations.

Lambda Expressions in Unity

In Unity, lambda expressions are useful in many scenarios:

  1. Sorting and filtering collections of game objects, components, or custom data
  2. Event handlers for UI elements and custom events
  3. Callback functions for asynchronous operations
  4. Custom animation curves and interpolation functions
  5. AI behavior conditions and actions
Unity Relevance

Unity's newer systems like the Input System package and UI Toolkit make extensive use of lambda expressions for event handling. Understanding lambdas will help you write cleaner, more maintainable code when working with these systems.

Best Practices for Lambda Expressions

  1. Keep them short and focused: Lambda expressions should generally be brief. If your lambda grows complex, consider using a named method instead.

  2. Be mindful of variable capture: Remember that captured variables can lead to unexpected behavior, especially in loops.

  3. Use expression lambdas for simple operations: They're more concise and clearly express intent.

  4. Use statement lambdas for more complex logic: When you need multiple statements or control flow.

  5. Consider readability: Sometimes a well-named method is clearer than an inline lambda, especially for complex operations.

Conclusion

Lambda expressions are a powerful feature that brings functional programming concepts to C#. They provide a concise syntax for defining inline methods, making your code more readable and expressive. As you progress in your C# and Unity journey, you'll find lambda expressions indispensable for many programming tasks.

In the next section, we'll explore LINQ (Language Integrated Query), which builds on lambda expressions to provide powerful data querying capabilities.