Skip to main content
๐Ÿ“œ WAYPOINT LESSON

Expression trees vs delegates

โญโญโญ advancedโณ 15 min read๐Ÿ“ Lesson 173 of 265

Code as data: when to compile to IL, when to inspect, and the cost line between them.

Expression trees vs delegates

Expression<Func<T,bool>> is code as data: a tree of nodes you can inspect, transform, print, or compile. A Func<T,bool> is code as code: opaque, fast to invoke.

Expression<Func<int, bool>> e = x => x > 5;
var body = (BinaryExpression)e.Body;      // node inspection
Console.WriteLine(body.NodeType);         // GreaterThan

var f = e.Compile();                      // โ†’ IL via Lightweight Code Generation
f(7);                                     // true โ€” and now it runs fast

Decide by use, not by fashion:

| Need | Choose | |---|---| | call it many times, never look inside | delegate | | translate it (EF, LINQ providers, rules โ†’ SQL) | expression tree | | build rules from configuration at runtime | build Expression, Compile once | | hot path called in a tight loop | delegate (or compile the tree ONCE, cache it) |

Costs: building trees allocates node objects; Compile() is expensive โ€” do it once per rule, not per item. Expression trees cannot contain async, null-coalescing assignment, or ??= โ€” they model expression logic, not statements; when rules need statements, either restructure to expressions or generate code another way (source generators, Module 8).

The classic production bug: storing Expression<Func<...>> and invoking it directly (it's not a delegate), or compiling per call and wondering why the "fast" rule engine is slow. Both are one-line fixes once you can see the difference between the two types.