Code Focused

Tighten up Your Visual Basic Code with Lambda Expressions

See how lambda expressions enhance a common programming scenario.

A friend was recently reviewing some Visual Basic code that I was having an issue with. As we looked at one particular section of code, he said "You could really tighten up this code with a couple lambda expressions. I'd do that right away." He said it so matter-of-fact that it really took me by surprise. I freely admit that lambda expressions are not forefront in my everyday programming vocabulary, and I rarely use them in my code. What did he know that I didn't?

I certainly am aware of lambda expressions; I'd explored how lambda expressions work when they became available. Lambda expression support appeared together in VB 9.0 and C# 3.0 with the release of Visual Studio 2008 and the .NET Framework 3.5 in November 2007. Lambda expressions were introduced into both languages to support Language Integrated Query (LINQ), which was released at the same time.

While I have no empirical evidence, my impression is that C# developers are more comfortable with structural elements like anonymous delegates and lambda expressions than Visual Basic developers. Case in point: my friend that mentioned lambda expressions when reviewing my code is primarily a C# developer. The Visual Basic language has a tradition of abstracting the inner workings of the language; perhaps Visual Basic developers like me have been spoiled by constructs like the HANDLES keyword.

To explore and evaluate how lambda expressions could play a more significant -- and hopefully beneficial -- role in coding, I use a common scenario I've coded several times, but this time taking full advantage of Lambda expressions in my source code. This scenario involves a search screen that supports search by the item ID code or item name, by either an exact search or an incremental search. This search screen code can be used for any list of items with multiple properties to be searched.

The sample screen shown in Figure 1 displays a list comprised of both a randomly-generated ID code and six-character name; both are displayed to help illustrate the search functionality. The code download was developed in Visual Studio 2010, targeting the .NET Framework 3.5 Client Profile.


[Click on image for larger view.]
Figure 1. The search screen.

The first task in our search scenario is to generate random data to be searched. The elements in the list consist of instances of the SearchItem class with an overridden ToString() method to show both the Code and Name.

Public Class SearchItem
    Public Property Code As Integer
    Public Property Name As String
    Public Overrides Function ToString() As String
        Return String.Format("{0} - {1}", Code, Name)
    End Function
End Class

The code to generate the 1,000 rows of sample data is below. In the constructor, it generates an unsorted list of data, then keeps additional copies pre-sorted by code and by name in order to allow the search screen to alternate quickly between the displayed orders.

Typical code without Lambda expressions is below. Note the inner loop to generate each random character and the full LINQ expression to create the ByCode and ByName sorted versions of the List. Prior to LINQ, I would have used a System.Collections.SortedList and its GetKeyList method to sort the elements into the proper order.

Public Class SearchData
    Public Property ByCode As New List(Of SearchItem)
    Public Property ByName As New List(Of SearchItem)
    Public Property UnSorted As New List(Of SearchItem)
    Public Sub New(NameLength As Integer)
        For i = 1 To 1000
            Dim RandomLowerCaseLetters = ""
            For j = 1 To NameLength
                RandomLowerCaseLetters += Chr(Asc("a") + Rnd() * 25)
            Next
            UnSorted.Add(New SearchItem With {.Code = Rnd() * 999999, .Name = RandomLowerCaseLetters})
        Next
        ByCode = (From item In UnSorted Order By item.Code Select item).ToList
        ByName = (From item In UnSorted Order By item.Name Select item).ToList
    End Sub
End Class

Below is the same class with the use of lambda expressions to generate the random character name. It replaces the For loop with an Enumerable.Range that generates a collection consisting of NameLength items, each provided to the lambda expression that generates the random letter. The outer String.Concat() concatenates all the generated random letters into a single string. Lower in the code, the ByCode and ByName copies are sorted with lambda expressions, using a tighter and arguably more readable syntax.

Public Class SearchData
    Public Property ByCode As New List(Of SearchItem)
    Public Property ByName As New List(Of SearchItem)
    Public Property UnSorted As New List(Of SearchItem)
    Public Sub New(NameLength As Integer)
        For i = 1 To 1000
            Dim entry = New SearchItem With {
                .Code = Rnd() * 999999,
                .Name = String.Concat(
                    	Enumerable.Range(0, NameLength).Select( 
		Function(x) Chr(Asc("a") + Rnd() * 25)).ToArray())
            }
            UnSorted.Add(entry)
        Next
        ByCode = UnSorted.OrderBy(Function(x) x.Code).ToList()
        ByName = UnSorted.OrderBy(Function(x) x.Name).ToList()
    End Sub
End Class

The logic to find the index of the matching Code value benefits from Lambda expressions. The lambda expressions for an exact match and an incremental match differ by only the comparison operator, but both pack a lot of functionality into a single line of code.

Public Shared Function FindFirstByCode(SearchList As List(Of SearchItem), _
		SearchCode As Integer, Exact As Boolean) As Integer
  Dim result = -1
  If Exact Then
      result = SearchList.FindIndex(Function(x) x.Code = SearchCode)
  Else
      result = SearchList.FindIndex(Function(x) x.Code >= SearchCode)
  End If
  Return result
End Function

The code to find the index of the matching Name value benefits from Lambda expressions as well. The second Lambda to search for a partial name match demonstrates a multi-line lambda function, improving readability of the function and providing the opportunity to express more complex logic than a single line function. Figure 2 shows the code in action.

Public Shared Function FindFirstByName(SearchList As List(Of SearchItem), _
		SearchName As String, Exact As Boolean) As Integer
        Dim result = -1
        If Exact Then
            result = SearchList.FindIndex(Function(x) x.Name = SearchName)
        Else
            result = SearchList.FindIndex(
            Function(x)
                Dim MatchLength = Math.Min(Len(SearchName), Len(x.Name))
                Return x.Name.Substring(0, MatchLength) = SearchName
            End Function)
        End If
        Return result
    End Function
End Class

[Click on image for larger view.]
Figure 2. Incremental search using lambda expressions.

The only remaining source code in our search screen scenario is form code-behind to manage the screen and call the functions shown.

Lambda expressions were introduced to support LINQ, but we've seen that they can be useful in replacing procedural code with functional expressions that describe "what" to be done, and not "how". These unnamed subroutines or functions exist only within the scope of the outer routine.

Like any programming construct, overuse can be counter-productive; but used judiciously, lambda expressions can reduce the amount of procedural code needed and help bring clarity and conciseness to your source code. My reviewer was correct -- lambda expressions did tighten up my code.

About the Author

Joe Kunk is a Microsoft MVP in Visual Basic, three-time president of the Greater Lansing User Group for .NET, and developer for Dart Container Corporation of Mason, Michigan. He's been developing software for over 30 years and has worked in the education, government, financial and manufacturing industries. Kunk's co-authored the book "Professional DevExpress ASP.NET Controls" (Wrox Programmer to Programmer, 2009). He can be reached via email at [email protected].

comments powered by Disqus

Featured

Subscribe on YouTube