Null Propagation
Lambda Expressions
Programming Languages
Syntax Error
C# Operators

Why can't I use the null propagation operator in lambda expressions?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

This error usually appears when a lambda is being compiled as an expression tree rather than as executable delegate code. In regular C# code, the null propagation operator works well, but expression trees support only a subset of language constructs. Understanding that distinction explains both the error and the correct fix.

Delegate Lambdas vs Expression Tree Lambdas

The same lambda syntax can target two different runtime representations.

  • A delegate lambda compiles to executable IL.
  • An expression lambda builds a data structure that another component can inspect and translate.

IEnumerable LINQ uses delegates. IQueryable LINQ usually uses expression trees so query providers can translate logic to SQL or another backend.

csharp
1using System;
2using System.Linq.Expressions;
3
4public class Person
5{
6    public Address? Address { get; set; }
7}
8
9public class Address
10{
11    public string? City { get; set; }
12}
13
14public class Demo
15{
16    public static void Main()
17    {
18        // Delegate lambda: this is executable code.
19        Func<Person, string?> ok = p => p.Address?.City;
20
21        // Expression lambda: provider-friendly shape required.
22        Expression<Func<Person, string?>> expr =
23            p => p.Address == null ? null : p.Address.City;
24
25        Console.WriteLine(ok(new Person { Address = new Address { City = "Toronto" } }));
26        Console.WriteLine(expr);
27    }
28}

When an expression tree is required, ?. may trigger a compile error because there is no equivalent node in the expression tree model used by the target provider.

Why Query Providers Reject ?.

Expression trees are not the same as source code text. They are a tree of known node types such as member access, conditional, call, and constant. Many language features are lowered by the compiler only when emitting executable code. Query providers need explicit nodes they know how to translate.

For null-safe access, a ternary expression produces a shape that providers understand.

csharp
Expression<Func<Person, string?>> cityExpr =
    p => p.Address == null ? null : p.Address.City;

This is semantically clear and usually translatable by ORMs such as Entity Framework.

Practical Rewrite Patterns

Use explicit null checks for expression tree scenarios.

csharp
1// Pattern 1: one level
2p => p.Address == null ? null : p.Address.City
3
4// Pattern 2: two levels
5p => p.Address == null
6    ? null
7    : (p.Address.City == null ? "Unknown" : p.Address.City)

If the expression becomes deeply nested, project intermediate values in two steps to keep readability high.

csharp
1using System.Linq;
2
3var query = context.People
4    .Select(p => new
5    {
6        Person = p,
7        Address = p.Address
8    })
9    .Select(x => new
10    {
11        City = x.Address == null ? null : x.Address.City
12    });

This pattern often produces cleaner SQL translation than a single complex expression.

When ?. Is Fine

You can still use null propagation freely in normal in-memory logic.

csharp
1var people = new[]
2{
3    new Person { Address = new Address { City = "Paris" } },
4    new Person { Address = null }
5};
6
7var cities = people.Select(p => p.Address?.City ?? "Unknown");
8foreach (var c in cities)
9{
10    Console.WriteLine(c);
11}

Here the query runs against IEnumerable, so it executes as normal C# code and no expression tree translation is needed.

Common Pitfalls

A common pitfall is assuming all LINQ queries are equivalent. Queries over IQueryable and IEnumerable can accept different syntax because one is translated and the other is executed directly. Another pitfall is writing provider-specific expressions without tests. Always validate against your real provider because translation support differs across versions. A third issue is overusing AsEnumerable as a quick fix. It avoids translation errors but may pull large data sets into memory, causing performance problems. Finally, when rewriting null logic, avoid changing semantics by accident. Keep unit tests for null and non-null paths to catch regressions.

Summary

  • The null propagation operator can fail inside expression tree lambdas.
  • The root cause is representation limits and provider translation rules.
  • Rewrite with explicit conditional expressions for reliable translation.
  • Keep ?. for delegate and in-memory scenarios where it works naturally.
  • Test null behavior and query performance after rewriting.

Course illustration
Course illustration

All Rights Reserved.