heap$ dotnet run23x => x > limitMethodTargetisBig1int limit = 5;2Func<int, bool> isBig = x => x > limit;3int[] nums = [3, 8, 1, 9];4int n = nums.Count(isBig);5Console.WriteLine(n);6limit = 2;7Console.WriteLine(nums.Count(isBig));closurelimit5Countn–[0]3[1]8[2]1[3]9=>3819truefalse232
Front: the code and LINQ's CountBack: the lambda's method; right: the heap, holding the delegate, the closure and the arrayTokens: the lambda, the delegate, each element, true/false and the results moving throughFront: the code, Program.cs, and LINQ's Count with its result n. Back: the console and the lambda's compiled method. Right: the heap, which holds every object: the delegate object isBig, the closure that holds limit, and the array nums. Line 2 makes a delegate whose Method is the lambda and whose Target is the closure; Count calls it once per element (n = 2); after limit = 2 the same delegate answers 3. The closure object is how the C# compiler implements captured variables today: it generates a hidden class with limit as a field, and line 1 writes that field. Its name and shape are compiler details; the language only promises that the lambda and main share one variable.

Delegates and lambdas

  1. A delegate is a type whose values are methods. Func<int, bool> is a ready-made delegate type in .NET: it fits any method that takes an int and returns a bool. Line 2 declares isBig with that type.

Objects on the heap

Calling it

Captured variables

What happens

// step 1/11 · delegate
Func<int, bool>      a delegate type: int in, bool out
  Func<T, TResult>     built into .NET (namespace System)
  fits: bool IsEven(int x), bool IsBig(int x), ...
  Func<int, bool> isBig = x => x > limit;
isBig can hold (a reference to) any method of that shape

The closure object is how the C# compiler implements captured variables today: it generates a hidden class with limit as a field, and line 1 writes that field. Its name and shape are compiler details; the language only promises that the lambda and main share one variable. Delegate.Target and Delegate.Method are real properties. For this capturing lambda, Target is the closure object; for a lambda that captures nothing, the compiler may reuse one cached delegate object instead of making a new one.