Two of C#’s really interesting features are technically operators, but didn’t fit in last week’s article. These are both ways to create anonymous functions: lambdas and delegates. AS3 has anonymous functions too, but today’s article will discuss how they differ from the C# approaches. Read on to learn how to harness the power of anonymous functions in C#.

Table of Contents

Let’s start with AS3’s anonymous function support. You can create these with a syntax just like you use to create normal functions. Here’s how that looks:

public function double(values:Vector.<int>): void
{
	function doDouble(value:int): int { return value + value; }
 
	for (var i:int = 0; i < values.length; ++i)
	{
		values[i] = doDouble(values[i]);
	}
}
 
var values:Vector.<int> = new <int>[1, 2, 3];
double(values);
trace(values[0] + ", " + values[1] + ", " + values[2]);
// output: 2, 4, 6

Alternatively, you can omit the function’s name and choose to store it as a local variable with the Function type:

public function double(values:Vector.<int>): void
{
	var doDouble:Function = function(value:int): int { return value + value; };
 
	for (var i:int = 0; i < values.length; ++i)
	{
		values[i] = doDouble(values[i]);
	}
}
 
var values:Vector.<int> = new <int>[1, 2, 3];
double(values);
trace(values[0] + ", " + values[1] + ", " + values[2]);
// output: 2, 4, 6

You could also pass the function as a parameter with the Function type:

public function double(values:Vector.<int>): void
{
	for (var i:int = 0; i < values.length; ++i)
	{
		transformElement(
			values,
			i,
			function(value:int): int { return value + value; }
		);
	}
}
 
public function transformElement(
	values:Vector.<int>,
	index:int,
	transformer:Function
): void
{
	values[index] = transformer(values[index]);
}
 
var values:Vector.<int> = new <int>[1, 2, 3];
double(values);
trace(values[0] + ", " + values[1] + ", " + values[2]);
// output: 2, 4, 6

In all cases, you end up with calls to an untyped Function variable. That means the compiler has no way of knowing how many parameters the function takes, what the types of the parameters are, if the function is even valid (e.g. not null), if there is a return type, or what the return type is. Everything about the function is dynamic. This means that you don’t get any compile-time error checking and your code runs extremely slowly in comparison to normal functions, especially when you consider that they often entail activation objects.

In contrast, C#’s anonymous functions are strongly-typed. They are not just a generic Function object like in AS3, but exactly like any other C# function. You therefore get compile-time error checking and full runtime speed.

Let’s start with delegates, which are similar in syntax to AS3’s anonymous functions. In part 9 of this series we talked about delegates as a way of declaring a strongly-typed kind of function. Today, we’ll look at the alternate syntax that allows us to create strongly-typed anonymous functions. Here’s how it looks:

private delegate int Doubler(int value);
 
public void Double(int[] values)
{
	Doubler doDouble = delegate(int value) { return value + value; };
 
	for (int i = 0; i < values.Length; ++i)
	{
		values[i] = doDouble(values[i]);
	}
}
 
int[] values = new int[]{ 1, 2, 3 };
Double(values);
Debug.Log(values[0] + ", " + values[1] + ", " + values[2]);
// output: 2, 4, 6

Notice that the type of doDouble is now Doubler, a delegate we’ve declared elsewhere. The declared delegate is strongly-typed since we declared its parameters and return value. This means that the doDouble anonymous function is also strongly-typed. For example, if you tried to pass a string to doDouble you’ll get a compiler error. Here’s how you’d pass a delegate as a parameter:

public delegate int Transformer(int value);
 
public void Double(int[] values)
{
	for (int i = 0; i < values.Length; ++i)
	{
		TransformElement(
			values,
			i,
			delegate(int value) { return value + value; }
		);
	}
}
 
public void TransformElement(
	int[] values,
	int index,
	Transformer transformer
)
{
	values[index] = transformer(values[index]);
}
 
int[] values = new int[]{ 1, 2, 3 };
Double(values);
Debug.Log(values[0] + ", " + values[1] + ", " + values[2]);
// output: 2, 4, 6

Next up are lambdas, which are really just a syntactical short-cut for delegates. Instead of using the keyword delegate, you use the => operator:

public delegate int Doubler(int value);
 
public void Double(int[] values)
{
	Doubler doDouble = (int value) => { return value + value; };
 
	for (int i = 0; i < values.Length; ++i)
	{
		values[i] = doDouble(values[i]);
	}
}
 
int[] values = new int[]{ 1, 2, 3 };
Double(values);
Debug.Log(values[0] + ", " + values[1] + ", " + values[2]);
// output: 2, 4, 6

But the shortcuts don’t end there. Lambdas can be really concise if you want them to be. Let’s break down the doDouble lambda one step at a time. First, you don’t need to declare the parameter’s type since that’s already known from the declaration of the delegate:

Doubler doDouble = (value) => { return value + value; };

And if there’s only one parameter, you don’t need parentheses around the parameters list:

Doubler doDouble = value => { return value + value; };

A usual naming convention for lambdas—which are supposed to be very small functions—is to use one-letter parameter names:

Doubler doDouble = v => { return v + v; };

Finally, if there’s only one statement in the function then you can remove the curly braces and the return keyword:

Doubler doDouble = v => v + v;

So we’ve reduced our delegate from 45 characters to a lambda with only 10 characters while retaining the strong typing and, arguably, the expressiveness. But sometimes it gets even shorter. When passing a lambda as a parameter rather than storing it as a variable, you can skip the whole variable declaration part:

public delegate int Transformer(int value);
 
public void Double(int[] values)
{
	for (int i = 0; i < values.Length; ++i)
	{
		TransformElement(values, i, v => v + v);
	}
}
 
public void TransformElement(
	int[] values,
	int index,
	Transformer transformer
)
{
	values[index] = transformer(values[index]);
}
 
int[] values = new int[]{ 1, 2, 3 };
Double(values);
Debug.Log(values[0] + ", " + values[1] + ", " + values[2]);
// output: 2, 4, 6

That wraps up C#’s support for anonymous functions via delegates and lambdas. The main disadvantage comapared to AS3’s anonymous functions is that they require strong parameter and return types rather than the extreme flexibility of the Function class. The main advantage is that they have strong parameter and return types and you therefore get compile-time error checking and improved performance. A secondary advantage comes in the form of lambdas with a much more terse syntax without losing the main advantage of type safety.

To summarize, the following side-by-side code snippets show the approaches that C# and AS3 take to supporting anonymous functions:

////////
// C# //
////////
 
// Declare anonymous function's type
delegate int BinaryOperation(int a, int b);
 
// Declare anonymous function
BinaryOperation sum = delegate(int a, int b) { return a + b; }
 
// Function taking anonymous function
int DoOperation(int a, int b, BinaryOperation op)
{
	return op(a, b);
}
 
// Pass anonymous function as a parameter
DoOperation(1, 2, delegate(int a, int b) { return a + b; });
 
// Declare lambda
BinaryOperation sum = (a,b) => a + b;
 
// Pass lambda as a parameter
DoOperation(1, 2, (a,b) => a + b);
/////////
// AS3 //
/////////
 
// Declare anonymous function's type
// {unnecessary in AS3}
 
// Declare anonymous function
var sum:Function = function(a:int, b:int): int { return a + b; }
 
// Function taking anonymous function
function doOperation(a:int, b:int, op:Function): int
{
	return op(a, b);
}
 
// Pass anonymous function as a parameter
doOperation(1, 2, function(a:int, b:int): int { return a + b; });
 
// Declare lambda
// {unsupported in AS3}
 
// Pass lambda as a parameter
// {unsupported in AS3}

Next week we’ll continue the series with even more of the kind of code you can put in your functions. Stay tuned!

Continue to Part 17

Spot a bug? Have a question or suggestion? Post a comment!