Now that we've seen many concrete concepts that C# implements, we need to take some time to talk about a few more abstract ideas. These include expressions, lambdas, and delegates, and in this post we'll discuss each of these and show examples. Let's go!

A little funny experiments with hands and eyes
Would this be happy, neutral, or concerned? Photo by Franco Antonio Giovanella / Unsplash

The Sample Project

exceptionnotfound/CSharpInSimpleTerms
Contribute to exceptionnotfound/CSharpInSimpleTerms development by creating an account on GitHub.
Project for this post: 18Lambdas

Expressions

An expression is a nebulous idea in C#: it is a group of operators and operands. An expression can be a value, an assignment, a variable, an operation, a method invocation, a property access, and more.

long myLong = 6444296L; //Value
DateTime myDate = DateTime.Now.AddDays(7); //Method invocation
var sum = 6 + 7; //Operation
short? myNull = null; //Null
bool hasValue = myNull.HasValue; //Property access
//All of the above are also assignment expressions.

In the example above, both 6444296L and long myLong = 6444296L; are expressions, since both values and assignments are expressions. Similarly, DateTime.Now.AddDays(7) is an expression, as is 6 + 7 and myNull.HasValue, and each of their corresponding assignments are also expressions unto themselves.

Expressions can be combined to form other expressions:

var a = 10; //Assignment
var b = a + 5; //Assignment and addition operation
var c = b % 5 == 0 ? b : a; //Assignment, modulus operation, and conditional
We learned about the conditional operator ?: in the Operators post.

The last line in the above example breaks down into four expressions: a modulus operation %, an equality operation ==, a conditional operation ?:, and an assignment =. Remember this line; we will use it again later in this post.

In short, an expression produces a result and can be included in another expression. An exception to this is void, since methods which return that can still be included in an expression.

Console.WriteLine("Hello world!"); //An expression which returns void.

Interpolated Strings ($)

The operator $ is called the interpolated string operator and is itself an expression. We can use it like so:

var height = 5;
var width = 10;

var message = $"The area of a rectangle with width {width} and height {height} is {width * height}";
Console.WriteLine(message);

//Output: The area of a rectangle with width 5 and height 10 is 50.

When using this operator, we can place variables or values directly in a string, surrounded by braces {}. The string will evaluate the values or variables and include the results in the string itself.

Lambda Expressions

Lambda expressions are any expression that uses the operator =>, which we read aloud as "goes to". You have already seen some examples of this operator in the LINQ Basics post; in fact, LINQ is where most of the lambda expressions you write will exist.

var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var oddNumbers = numbers.Where(x => x % 2 != 0);
var sumOfEven = numbers.Where(x => x % 2 == 0).Sum();

The true definition of a lambda expression in C# is any piece of code that uses the => operator AND has either an expression or a code block as its body.

(input-parameters) => expression

(input-parameters) => { code_block }

The idea is that the input parameters will be used by the expression or code block, and the result of that expression or code block (if one exists) will be returned.

Delegates

Any lambda expression is also a delegate, which for the purposes of this article is a code block or expression that will optionally take inputs and return an output. That may sound like a method, but delegates do not behave like methods.

We can create our own delegates using the => operator and the Func<T> class, which is called an expression lambda since the body of the lambda is a single expression.

var num = 64;
Func<int, double> squareRoot = x => Math.Sqrt(x);
Console.WriteLine($"Square Root of {num} is {squareRoot(num)}");
//Square Root of 64 is 8

num = 144;
Console.WriteLine($"Square Root of {num} is {squareRoot(num)}");
//Square Root of 144 is 12

A Func<T> defines the type of each input in order first, and then the type of the output. In the example above, the squareRoot function takes an input of type int and outputs a value of type double.

We can use the type Action<T> when we want to use a code block instead of a single statement and where we do not want to return a value from a delegate. This is called a statement lambda since the body of the lambda is a collection of statements (AKA a code block). Note that delegates can access variables that are declared outside of the delegate:

List<string> names = new List<string>();

Action<string> sayHi = name =>
{
    Console.WriteLine($"Hi {name}!");
    names.Add(name);
};

sayHi("Nicky");
sayHi("Ravi");
sayHi("Danielle");

Delegates can also be created using the delegate keyword, however this is not the recommended way to create them, and thus examples using that keyword will not be included in this post.

A Delegate Example

Lambdas, delegates, and expressions save us from having to write a lot of code. Consider the following lines, which we saw earlier in this post.

var a = 10; //Assignment
var b = a + 5; //Assignment and addition operation
var c = b % 5 == 0 ? b : a; //Assignment, modulus operation, and conditional

The last line is the most interesting, because it breaks down into four separate expressions (modulus, boolean compare, conditional, and assignment). Let's create a Func<T> delegate for each expression, not including the assignment. We can also create a combined Func<T> which calls the individual functions in order.

Func<int, int> modulus = x => { return x % 5; };
Func<int, bool> boolCompare = x => { return x == 0; };
Func<bool, int, int, int> conditional 
    = (isTrue, x, y) => { return isTrue ? x : y; };
    
Func<int, int> combined = x =>
{
    var modResult = modulus(x);
    var boolResult = boolCompare(modResult);
    return conditional(boolResult, 1, 0);
};

Console.WriteLine(combined(25)); //1
Console.WriteLine(combined(36)); //0

If this seems ridiculous, that's because it is. If this code is only expected to be used one time, making the individual components of it into delegates is overkill. On the other hand, if we need to do this multiple times, having these delegates already defined can be useful. We can therefore see both sides of delegates: they can make commonly-called code easier to invoke, but they also make our code more complex.

Please note that delegates have much more functionality than what is described here; you can find more information about them in the official Microsoft documentation:

Introduction to Delegates
Learn about delegates in this overview topic that introduces basic concepts and discusses language design goals for delegates.

Glossary

  • Expression - Produces a result and can be used in other expressions. This includes values, variables, method invocations, property accesses, and more.
  • Lambda expression - A specific kind of expression that uses the "goes to" operator =>. Code in the body of a lambda can be invoked later in the execution.
  • Delegates - A set of code that will be dynamically invoked later in the execution. Can be either a single or multiple expressions.

New Keywords and Operators

  • delegate - Creates a new delegate. We prefer to use lambdas => when creating a delegate instead of this keyword.
  • $ - Interpolated string operator. Allows us to "insert" values and variables into a string and have the values inserted into the string.

Summary

Expressions are groups of operators and operands, which can be mere values, operations, variables, method invocations, and more.

Lambda expressions are any expression that uses the operator =>, and are commonly used in LINQ statements.

Lambda expressions are also examples of delegates, which are collections of lines of code that can take inputs and optionally return outputs. We use the type Func<T> to define delegates that return an output, and Action<T> for delegates that will not return an output. Func<T> and Action<T> can themselves be combined into other delegates.

Postscript

In the original plan for the C# In Simple Terms series, this post was supposed to be where the series ended. However, there is much more to learn about C#, and so there are four more posts to be published! The first is about string manipulation and cultures, the second is about dates and times and how we can represent them, and the last two are about indexers and iterators respectively.

Because these posts were not part of the original plan, they will come out weekly instead of twice-a-week, starting next Monday, November 30th. We'll see you then!

Happy Coding!