Closures and the Lambdas
This C# Guide clarifies Lambda Functions and Closures, as well as demonstrating the capabilities and advantages of using them together.
Sep 16, 2019 • 5 Minute Read
Introduction
In C#, the capability that allows a method or a function to reference a non-local variable or value is called closure. This feature is commonly utilized in conjunction with the lambda functions which are nothing more than anonymous functions that developers utilize over named function to provide a way to add flexibility. This guide will make the terms of lambda functions and closures clear, and also demonstrate the capabilities of these very important features.
Non-local Variables
In C#, accessing a variable outside the scope of a function is possible and considered normal. Variables accessed this way are called non-local variables. You may also hear developers referring to this type of variable as captured variable. They refer to the same type of variable.
class LambdasNClosure
{
static string platform = "Pluralsight";
static void WhoIsTheBest()
{
Console.WriteLine($"{platform} is the best!");
}
static void Main(string[] args)
{
WhoIsTheBest();
Console.ReadKey();
}
}
Calling the function gives us the following results.
Pluralsight is the best!
This demonstrated the concept called closure.
Lambda Functions
There are lots of articles about lambdas being closures and some people tend to agree that they are only part of the truth. I strengthen the camp of those in the opposition. Lambda functions may be implemented as closures, but they are not closures themselves. This really depends on the context in which you use your application and the environment. When you are creating a lambda function that uses non-local variables, it must be implemented as a closure.
Lambda expressions can basically take two forms.
- Expression lambdas
- Statement lambdas
Expression lambdas usually look like this:
Func<double, double, bool> isItEqual = (x,y) => x == y;
Technically, we define the input and output types and provide an expression which is evaluated based on the argument, then it is returned as a result. Sometimes the compiler cannot infer the input types, so you may want to be on the safe side and specify them like this.
Func<double, double, bool> isItEqual = (double x, double y) => x == y;
Statement lambdas usually look like this:
Action<string> Welcome = name =>
{
Console.WriteLine($"Welcome {name} to the Pluralsight platform!");
};
Note how the closing curly brace also ends in a semicolon. When we work with statement lambdas, we usually create multiple expressions - encapsulate them inside our function. These are the best practices for creating an atomic operation; they exist for a single purpose/action.
Now we take a sharp turn and combine lambdas and closures.
public static partial class ClosureDemo {
public static void Outside()
{
string notLocal = "Closure based lambda!";
Func<string> demo = () =>
{
string local = "This is lambda, ";
return local + notLocal;
};
string message = demo();
Console.WriteLine($"The burrowed message: {message}");
}
}
class LambdasNClosure
{
static void Main(string[] args)
{
ClosureDemo.Outside();
Console.ReadKey();
}
}
The output is as follows:
The burrowed message: This is lambda, Closure based lambda!
In this example our non-local variable did not change, however, this situation might not always be true. If the variable changes, the referencing function will be impacted. Sometimes this is exactly the case we want to achieve. For example, we could have a function that logs the internal change in a function's state with a lambda function. That way, each change to the state will result in a different log.
Closures
Under the hood closure is nothing more than a syntactic sugar that allows access to a non-local variable. This is very convenient, however, it comes with a price. Another wording for closure which is fairly common is that it's a block of code which maintains its environment and can be called at a later point in time.
Let's demonstrate it with a tiny code.
static void Main(string[] args)
{
int foo = 10;
Func<int> Bar = () => { return foo; };
Console.WriteLine($"Foo: {Bar()}");
foo = 20;
int fighter = Bar();
Console.WriteLine($"Fighter: {fighter}");
}
The output shows the following results:
Foo: 10
Fighter: 20
Conclusion
Closures can be underwhelming, as alone they do not seem to provide too much extra functionality or gain. Their power becomes apparent only when you combine them with libraries which take advantage of their nature. Letting you express specific behaviors at the right places. I hope this was informative for you and that you found what you were looking for.