Introduction
Functional programming is a programming paradigm that treats application operations as a sequential execution of functions. Basically, your code becomes a huge math operation. But wait, is not as bad as it sounds. In C#, this is achieved by using delegate functions, lambda expressions and LINQ.
For example, consider the following code:
void Main()
{
int result = Sum(1, 2);
Console.WriteLine("1 + 2 = " + result);
}
Basically, the previous code simply performs an addition of a couple of numbers and then displays the result in the application console. By using functional programming, we could rewrite the previous code as follows:
public void Main()
{
var result = () => 1 + 2;
Console.WriteLine("1 + 2 = " + result.ToString());
}
Now, the addition result is assigned by executing an inline function. The way it is written (the arrow thingy) is called lambda notation.
Delegate Functions
Functional programming in C# has its roots in the concept of delegate functions, which are functions treated like regular variables and are to be used at any time the instantiator pleases, delegating a particular task to it. In other words, delegates are variables that instead of containing a value, it contains an executable method. The instantiator of the delegate can use it in all the ways a variable can be used, even passed as a method argument.
Since delegates are like regular variables, to create a delegate function we first have to declare its type. A delegate type declaration, however, is nothing like a regular class declaration. Is more like an interface method declaration; it defines the arguments and return type:
delegate int Sum(int left, int right);
Here, we declare a delegate type called Sum
which takes two integer arguments and returns another integer, representing the addition result. After we declare the delegate type, we can start creating instances of it.
delegate int Sum(int left, int right);
void Main()
{
Sum sum = delegate(int left, int right)
{
return left + right;
};
}
We just created a delegate function of type Sum
, which performs the integer addition and returns the resulting value. The real magic can be seen when you are able to use this delegate as a variable and call it as a method whenever necessary.
delegate int Sum(int left, int right);
void Main()
{
Sum sum = delegate(int left, int right)
{
return left + right;
};
int total = 0;
int[] numbers = { 1, 2, 3 };
foreach(int number in numbers)
{
total = sum(total, number);
}
}
As mentioned before, a delegate can be used in any way a traditional type can be used: variables, method parameters, properties and class-level fields. This represents a huge advantage, in terms of encapsulation. Methods can take a delegate type parameter and execute it without knowing what is really going on inside of it, more like a black box. Since the method knows the return type and arguments, it should not care about its inner workings.
Consider the following piece of code:
delegate int Sum(int left, int right);
class MathConsole
{
static void ShowSumInConsole(Sum sum, int left, int right)
{
int result = sum(left, right);
Console.WriteLine("Result: " + result.ToString());
}
}
In the previous example, the caller of the ShowSumInConsole
function must supply a delegate function as the sum
argument that will be in charge of performing the number addition, regardless of how it does it; and return the resulting integer. At some point, the ShowSumInConsole
will call it when the resulting integer is required.
In a way, this allows the caller to control how the number addition is to be performed. This is what we know as the principle of Inversion Of Control; or IoC for the folks. Basically, is a technique that allows the caller to take control of a particular operation executed in another function being called.
Sum sumWithTrace = delegate(int left, int right)
{
Trace.WriteLine("Adding " + left + " to " + right);
return left + right;
};
MathConsole.ShowSumInConsole(sumWithTrace, 2, 3);
Like aforementioned, we are assuming control of how to do the number addition; the ShowSumInConsole
is left only with the task to do exactly what its name says: show the addition result in the application console.
Another important thing to notice is that the ShowSumInConsole
does not know where the Sum
delegate function comes from or what it does to return the resulting integer, hence, it is known as an anonymous function. JavaScript developers can start to relate here.
Built-in Delegate Types
Previously, we created a custom type for our Sum
delegate. However, the .NET Framework provides several built-in delegate types for pretty much every of your needs. These are located in the System
namespace and the most commonly used are the Action
and the Func
delegate types.
Action Delegate Type
The Action
delegate type represents a delegate with no return value, or as we commonly know it: a void method. The signature looks as follows:
Action<in T>
Action<in T1.. in TN>
Consider the following delegate:
delegate void SendDataAction(string url, string data);
void Main()
{
SendDataAction sendData = delegate(string url, string data)
{
};
sendData("http://example.com", "Hello world!");
}
By using the Action
delegate type, we can rewrite the previous example as follows:
void Main()
{
Action<string, string> sendData = delegate(string url, string data)
{
};
sendData("http://example.com", "Hello world!");
}
As you could have figured out already, Action<string, />
is a delegate type for an inline method that takes two arguments of type string
. We can strip down the example even more by using lambda notation:
void Main()
{
Action<string, string> sendData = (url, data) =>
{
};
sendData("http://example.com", "Hello world!");
}
Now, things start to get interesting as we dive into the twisted dimension of lambda expressions. Here we don't have to specify the typed of the delegate arguments since we already did that when we specified what overload of the Action delegate type we were gonna use, in this case an Action<string, string>
, or an Action
delegate with two string arguments.
Function Delegate Type
A function delegate represents a delegate with return value and, optionally, one or more arguments. For these, we use the Func
delegate type, which signature looks as follows:
Func<out TResult>
Func<in T1.. in TN, out TResult>
In this particular type, the first type parameter, out T
, corresponds to the delegate return type. The rest of the type parameters represent each argument respectively.
Now, consider the following delegate:
delegate string GetDataFunction(string url);
void Main()
{
GetDataFunction getData = delegate(string url)
{
return data;
};
var data = getData("http://example.com");
Console.WriteLine("Returned data: " + data);
}
This time, we create a delegate that performs a request to the specified URL and then returns the response data to the caller as string. We can, however, rewrite the code to use the Func
type as follows:
void Main()
{
Func<string, string> getData = delegate(string url)
{
return data;
};
var data = getData("http://example.com");
Console.WriteLine("Returned data: " + data);
}
Or, in its simplest form, as a lambda expression:
void Main()
{
Func<string, string> getData = (url) =>
{
return data;
};
var data = getData("http://example.com");
Console.WriteLine("Returned data: " + data);
}
Again, when writing the delegate in lambda notation, we don't have to specify the types of the delegate arguments, we already did that by using a Func<string, string>
delegate type; or a Func
delegate with a single string argument and a string return type. Remember, the last type parameter always specifies the delegate return type.
Methods As Delegates
We already mentioned that some delegates are inline functions directly declared inside a method and such, but is important to know that method declarations in classes can be also delegates. For example, consider the following code:
class CarFactory
{
Car CreateCar(Func<Engine, string> engineFactory)
{
var engine = engineFactory("V8");
var car = new Car(engine);
return car;
}
}
In this particular case, the CreateCar
method takes a delegate as argument. This delegate will return a car engine instance for the specified engine name. We use a Func<Engine, string>
since it is expected to return an instance of the Engine
class and take a string as argument, representing the name of the engine to create. Now, consider the following code, which makes use of the CarFactory
class:
void Main()
{
CarFactory carFactory = new CarFactory();
Car car = carFactory.CreateCar(this.CreateEngine);
Console.WriteLine("Car brand: " + car.Brand);
}
Engine CreateEngine(string name)
{
if (name == "V8")
{
return new V8Engine();
}
else
{
throw new NotSupportedException();
}
}
As we can observe in this code, the CreateEngine
method is passed to the CreateCar
method in the CarFactory
class instance as a delegate of type Func<Engine, string>
. The CreateCar
method can use it, even if it belongs to the caller instance. So, the only rule we have to consider when using a method as a delegate is that the method have to comply with the delegate type parameters in order. In this case, CreateCar
requires a delegate with a return type of Engine
and a single argument of type string. The CreateEngine
method matches this requirements and can be effectively passed to the CreateCar
method so it uses it at its own will. Now we're sharing members between classes.
Lambda Expressions
In C#, a lambda expression is an inline function declared on-the-run and used as a regular variable. An example of a lambda expression is as follows:
void Main()
{
CarFactory carFactory = new CarFactory();
Car car = carFactory.CreateCar((name) =>
{
if (name == "V8")
{
return new V8Engine();
}
else
{
throw new NotSupportedException();
}
});
Console.WriteLine("Car brand: " + car.Brand);
}
Now, we don't declare methods to be used as delegates, we now write a lambda expression representing the delegate body to be executed by the CreateCar
method in an inline manner. This is known as lambda notation and is commonly used in LINQ, or Language Integrated Query, when using its chained methods form.
LINQ
LINQ is a feature of the C# language that allows the developer to write SQL-like queries to filter and retrieve data from a collection. For example:
void Main()
{
string[] names = { "John", "Douglas", "Albert" };
string[] smallNames = names.Where(name => name.Lengh <= 4).ToArray();
}
In the previous example, we use the Where
method which, internally, iterates through the collection and calls the supplied Func<string, bool>
delegate which is going to be supplied of the current name being evaluated and it is expected to return a boolean value indicating whether it satisfies the condition or not. Then, the resulting elements are converted to an array by using the ToArray
method, which is also part of LINQ.
On the other hand, it would be the same as writing the following code the old way:
void Main()
{
string[] names = { "John", "Douglas", "Albert" };
string[] smallNames = FilterNames(names, 4).ToArray();
}
IEnumerable<string> FilterNames(string[] names, int maxLength)
{
foreach(string name in names)
{
if (name.Length <= maxLength)
{
yield return name;
}
}
}
You might have noticed one of the main advantages of LINQ and delegates in general: It saves a whole bunch lines of code. Another advantage is that we keep the code simple and easy to read.
LINQ has several methods that extends the IEnumerable
interface, which is the interface implemented by every single collection type in the .NET Framework, such as arrays, lists and dictionaries. Some of these methods are:
-
Where
: Filters the source collection returning only the elements satifying the specified expression.
names.Where(name => name.StartsWith("L"));
-
OrderBy
: Orders the source collection using a sort expression.
persons.OrderBy(person => person.FirstName);
-
Take
: Gets a specified number of items from the source collection.
cars.Take(10);
-
Skip
: Skips a specified number of items in the source collection.
cars.Skip(10).Take(10);
-
Any
: Returns a boolean value indicating whether any element in the source collection meets the specified expression.
// Determine whether any account has SSN equal to 600-521-456. <br /> accounts.Any(account => account.SSN == "600-521-456");
These are some examples of LINQ methods. To use these, you have to include the System.Linq
namespace. Some other methods to consider are the collection conversion methods which are:
-
ToArray
: Converts the source collection to an array. -
ToList
: Converts the source collection to a strongly-typed list.
Conclusion
These are the basics of functional programming in C#. Basically, it comes to make a more readable code, which provides several benefits like code maintanability and ease of future development. Some topics are left out, such as predicate expressions and expression trees. But these will be covered in a future article.