There are a lot of ways to write code. Sometimes we write code as simply as possible, straight forward-perhaps inline calculations and sometimes we add levels of indirection. Indirection, or abstraction, makes code a bit more challenging to understand, but abstraction can add flexibility and a level of dynamism that can be very powerful and useful.
This article shows you how to use Lambda expressions as function arguments to support dynamic function behavior. If you program like this for critical aspects of your application then you will create a more flexible and powerful solution.
Most of the Time
A lot of code is written in a direct way. Suppose you have an application that needs to calculate sales tax. It is reasonable to write the calculation directly. For example, in Michigan the sales tax is currently 6%. To calculate sales tax you could write:
Dim total as Double = value * (1 + .06)
If this calculation is written inline it will solve the problem, but it does not represent a flexible or reusable solution. The next evolutionary improvement is to define a function that performs the calculation, and you could provide an optional argument. For example, you could move the calculation to a function and make the amount of tax optional, providing the most common default value for the tax amount. Listing 1 shows the code moved into a function.
Listing 1: Reusable code with a regular function and
optional default value.
Function CalculateSalesTax(ByVal total As Double, _ Optional ByVal tax As Double = 0.06) As Double Return total * (1 + tax) End Function
All but beginning programmers eventually get to writing code like the code shown in Listing 1 with the optional argument usage being employed eventually.
The solution in listing 1 is practical and suffices for a lot of scenarios. The only drawback is that it isn't flexible. You can pass in a total and a tax amount and the calculation is always the same. And, as I said, for routine solutions defining a well-named function is reusable and as a plus self-commenting. Sometimes though you need to write a solution that is even more flexible.
Some of the Time
Sometimes, especially if you are writing code that will be used by others, you need to write your code more flexibly. You can write your code to be more flexible by adding function parameters. Function parameters mean that part of the behavior will be passed in as a function. In general, use this approach if you aren't sure about all of the scenarios that the function must support.
You can define function parameters a couple of ways. You
can define a delegate and define the function--parameter
type as an argument of the delegate type or you can use one
of the pre-existing generic delegates like
Func. To provide the function-parameter you can
add an existing second function and the
AddressOf operator or you pass a Lambda
Expression. Listing 2 demonstrates how to define a function-
parameter using the pre-defined
delegate and satisfy that argument with a Lambda
Listing 2: A function with a function parameter satisfied
by a Lambda Expression.
Module Module1 Sub Main() Dim total = CalculateSalesTax(100, 0.06, _ Function(sale, tax) sale * (1 + tax)) Console.WriteLine(total) Console.ReadLine() End Sub Function CalculateSalesTax(ByVal total As Double, _ ByVal tax As Double, _ ByVal calculator As Func(Of Double, Double, Double)) _ As Double Return calculator(total, tax) End Function End Module
CalculateSalesTax receives (all of it's
behavior as the third argument. The third argument is
defined as Func(Of Double, Double, Double), a generic
delegate. Other generic delegates include Action(Of T) and
The calculator argument can be satisfied by any function that has two double arguments and double return type. This could be the address of a function, a Lambda Expression assigned to a variable of the same type--Func(Of Double, Double, Double)--or a literal Lambda Expression. In Listing 2 the calculator argument is satisfied by the Lambda Expression
Function(sale, tax) sale * (1 + tax)
Lambda Expressions are just very condensed functions. They use the Function keyword, parameter names with or without the parameter types, and the statement that performs the work. No End Function, no function name, and no return keyword are required.
The opportunity with writing code like this is that the consumer--the programmer using the code--can determine the behavior to pass to the function--in the example CalculateSalesTax. Assume for example that the sales tax is a fixed rate unless the dollar amount exceeds $50,000. For high priced items a luxury tax is imposed in some states. Then, the consumer could incorporate the luxury tax determinator in an alternate function. Listing 3 demonstrates an alternate implementation of the code (incorporating an arbitrary value for the luxury tax).
Listing 3: An alternate implementation of the solution in
Listing 2 that adds additional functionality.
Module Module1 Sub Main() Dim calculator = Function(sale, tax) IIf(sale > 25000, _ sale * (1 + tax + 0.02), sale * (1 + tax)) Dim total = CalculateSalesTax(100000, 0.06, calculator) Console.WriteLine(total) Console.ReadLine() End Sub Function CalculateSalesTax(ByVal total As Double, _ Optional ByVal tax As Double = 0.06) As Double Return total * (1 + tax) End Function Function CalculateSalesTax(ByVal total As Double, ByVal tax As Double, _ ByVal calculator As Func(Of Double, Double, Double)) As Double Return calculator(total, tax) End Function End Module
Notice that in the solution, a new Lambda Expression is assigned to an anonymous variable. The new Lambda Expression uses the IIf function to test whether or not the sale amount exceeds 25,000; if it does an additional amount is added to the tax amount representing a luxury tax.
The code in the solution uses a Lambda Expression and function-parameters. The solution is both reusable and very flexible because defining function-parameters means that future consumers can provide part of the solution.
Having as many ways of providing solutions as possible ensures that you are prepared for every day routine programming and exceptional programming cases. The key to great solutions is being aware of a wide variety of possible solutions and picking the best one for a given context.
Paul Kimmel is the VB Today columnist for CodeGuru.com and has written several books on object-oriented programming and .NET. Check out his upcoming book Professional DevExpress ASP.NET Controls (from Wiley) now available on Amazon.com and fine bookstores everywhere. Look for his upcoming book Teach Yourself the ADO.NET Entity Framework in 24 Hours (from Sams). You may contact him for technology questions at pkimmel@softconcepts .com. Paul Kimmel is a Technical Evangelist for Developer Express, Inc, and you can ask him about Developer Express at firstname.lastname@example.org and read his DX blog at http:// community.devexpress.com/blogs/paulk.