Take advantage of closures in C# — including anonymous methods, delegates, and lambda expressions — to make your code robust, efficient, readable, and easier to maintain. Credit: Chris Potter Closures are often associated with functional programming languages. Closures connect a function to its referencing environment, allowing the function to access non-local variables. In C#, closures are supported using anonymous methods, lambda expressions, and delegates. I have discussed anonymous methods and lambda expressions in previous articles. So what’s a delegate? A delegate is a type-safe function pointer that can reference a method that has the same signature as that of the delegate. Delegates are used to define callback methods and implement event handling. This article talks about how we can work with closures using anonymous methods, lambda expressions, and delegates in C#. To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here. Create a console application project in Visual Studio First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2019 is installed in your system, follow the steps outlined below to create a new .NET Core console application project. Launch the Visual Studio IDE. Click on “Create new project.” In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed. Click Next. In the “Configure your new project” window, specify the name and location for the new project. Click Create. We’ll use this project to illustrate the use of anonymous methods, lambdas, and delegates as closures in the subsequent sections of this article. A closure as a first-class function in C# A closure is defined as a first-class function containing free variables bound in the lexical environment. The C# programming language treats the first-class function as though it were a first-class data type. This means that you can assign the function to a variable, invoke it, or pass it around much the same way you work with any other first-class data type. A closure is a particular type of function that is intrinsically linked to the environment in which it is referenced. As a result, closures can use variables pertaining to the referencing environment, despite these values being outside the scope of the closure. Simple closure examples in C# You can write a closure using an anonymous method as shown in the code snippet given below. Func<string, string> someFunc = delegate (string someVariable) { return "Hello World!"; }; Alternatively you can create a closure using a lambda function as shown in the code snippet below. Func<string, string> someFunc = someVariable => "Hello World!"; Note that both of the above code snippets create a method that accepts a string as a parameter and returns another string. Here’s how you can invoke either of these closures we just created: string str = someFunc("This is a demo"); Let’s look at another example. The code snippet given below creates an integer value in a non-local variable named x. int x = 10; Action closure = delegate { Console.WriteLine("The value of the non-local variable x is: {0}", x); }; closure(); Here’s how you can do exactly the same thing using a lambda expression: int x = 10; Action closure = () => { Console.WriteLine("The value of the non-local variable x is: {0}", x); }; closure(); In both cases, the output will appear exactly as displayed in Figure 1 below. IDG Figure 1: Our simple closure in action! Closures capture variables, not values Because a closure is bound to the environment in which it is declared, it is able to reference out-of-scope variables and objects from within its body. Here is an example that illustrates this: int x = 10; Action a = delegate { Console.WriteLine($"The value of x is: {x}"); }; a(); When you execute the above code, the output will appear at the console window as shown in Figure 2. IDG Figure 2: A closure can reference non-local variables and objects within its body. The following code snippet shows that the anonymous method is bound to variables in the parent method body, not values. int x = 10; Action a = delegate { Console.WriteLine($"The value of x is: {x}"); }; x = 100; a(); When you execute the above code, the output will appear as shown in Figure 3. Note that the anonymous function returns 10, not 100. IDG Figure 3: A closure is bound to variables, not values, of the parent method body. How do C# closures work? When the C# compiler detects a delegate that forms a closure that is moved beyond the current scope, the delegate and its associated local variables are promoted to a compiler-generated class. From there, it just takes a little compiler magic to move between instances of the compiler-generated class, ensuring that each time the delegate is invoked, a function on this class is called. Once there are no longer any references to the instance of this class, the instance is garbage collected by the GC much the same way other instances are collected. Below is an example of a compiler-generated class created on compilation of a code snippet that contains closures. [CompilerGenerated] private sealed class <>c__DisplayClass0_0 { public int x; internal void <M>b__0() { Console.WriteLine(string.Format("The value of x is: {0}", x)); } } For a lambda to remain “callable,” the variables it references must survive even after the function in which they have been defined has finished executing. To achieve this, C# draws on classes. So, when a lambda function accesses a variable that is defined inside a function, that variable is extracted and placed inside a new class generated by the compiler. That’s exactly how a closure works! Closures originated in the world of functional programming but they have found a place in object-oriented programming languages as well. Closures don’t inherently provide composability — all they do is make it easier to implement delegates. I’ll have more to say about closures in future posts here. Related content feature 14 great preprocessors for developers who love to code Sometimes it seems like the rules of programming are designed to make coding a chore. Here are 14 ways preprocessors can help make software development fun again. By Peter Wayner Nov 18, 2024 10 mins Development Tools Software Development feature Designing the APIs that accidentally power businesses Well-designed APIs, even those often-neglected internal APIs, make developers more productive and businesses more agile. By Jean Yang Nov 18, 2024 6 mins APIs Software Development news Spin 3.0 supports polyglot development using Wasm components Fermyon’s open source framework for building server-side WebAssembly apps allows developers to compose apps from components created with different languages. By Paul Krill Nov 18, 2024 2 mins Microservices Serverless Computing Development Libraries and Frameworks news Go language evolving for future hardware, AI workloads The Go team is working to adapt Go to large multicore systems, the latest hardware instructions, and the needs of developers of large-scale AI systems. By Paul Krill Nov 15, 2024 3 mins Google Go Generative AI Programming Languages Resources Videos