Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB10

Getting started with LINQ

3.00/5 (2 votes)
2 Nov 2009CPOL6 min read 22.2K  
LINQ is syntactical sugar for extension methods and lamda expressions. To understand LINQ, it is important to first grapple with these concepts.To begin, let's look at a simple and somewhat common scenario.

LINQ is syntactical sugar for extension methods and lambda expressions. To understand LINQ, it is important to first grapple with these concepts.

To begin, let's look at a simple and somewhat common scenario. We have an unsorted list of names. We want to go through each letter of the alphabet and print an alphabetized list of names for the current letter. Here's a typical approach.

VB.NET
Module Module1
    Sub Main()
        Dim alphabet As String() = New String() _
		{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", _
                    "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", _
                    "U", "V", "W", "X", "Y", "Z"}
        Dim names As String() = New String() _
	{"Adam", "Dave", "John", "Alex", "Daryll", "Jacob", _
                          "Christopher", "Bill", "Ronald", "Jeff"}

        For Each letter In alphabet
            Dim selected_names As New List(Of String)

            For Each name As String In names
                If name.StartsWith(letter) Then selected_names.Add(name)
            Next

            If selected_names.Count > 0 Then
                selected_names.Sort(AddressOf SortNamesMethod)

                Console.WriteLine("Names beginning with '" & letter & "'")
                For Each name As String In selected_names
                    Console.WriteLine(name)
                Next
                Console.WriteLine("")
            End If
        Next

        'Pause for the user
        Console.Read()
    End Sub

    Private Function SortNamesMethod(ByVal name1 As String, _
		ByVal name2 As String) As Integer
        Return name1.CompareTo(name2)
    End Function
End Module

While this approach certainly accomplishes the job, you will see in a moment how LINQ can make the line count smaller, the program flow more logical and the code easier to maintain.

Extension Methods

Extension methods allow programmers to add useful methods to existing types. In our example, we will be adding an extension method to the IEnumerable(Of String) type. The extension method will print the list of strings to the console, as well as the list title.

In order to create an extension method, we will need to import the System.Runtime.CompilerServices namespace.

VB.NET
Imports System.Runtime.CompilerServices

Next we will need to create a new subroutine. The sub will be named ToConsole and will take two parameters. The first parameter will define the type to which this method is being added, and the second parameter will be the title that we want to give our list. Finally, we will need to add the Extension attribute to the function to alert the compiler that this method is an extension method.

VB.NET
<Extension()> _
    Private Sub ToConsole(ByVal items As IEnumerable(Of String), _
                          ByVal title As String)

    End Sub

Now we need to add the functionality. We will borrow it from the Main function above:

VB.NET
<Extension()> _
    Private Sub ToConsole(ByVal items As IEnumerable(Of String), _
                          ByVal title As String)
        If items IsNot Nothing Then
            Console.WriteLine(title)
            For Each item In items
                Console.WriteLine(item)
            Next
            Console.WriteLine("")
        End If
    End Sub

We can use this method on any object that implements the IEnumerable(Of String) interface. In our example, the selected_names variable in the Main method implements this interface because the List(Of String) implements IEnumerable(Of String). We can modify our code to look like this:

VB.NET
Imports System.Runtime.CompilerServices

Module Module1
    Sub Main()
        Dim alphabet As String() = New String() _
		{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", _
                    "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", _
                    "U", "V", "W", "X", "Y", "Z"}
        Dim names As String() = New String() _
		{"Adam", "Dave", "John", "Alex", "Daryll", "Jacob", _
                   "Christopher", "Bill", "Ronald", "Jeff"}

        For Each letter In alphabet
            Dim selected_names As New List(Of String)

            For Each name As String In names
                If name.StartsWith(letter) Then selected_names.Add(name)
            Next

            If selected_names.Count > 0 Then
                selected_names.Sort(AddressOf SortNamesMethod)

                selected_names.ToConsole("Names beginning with '" & letter & "'")
            End If
        Next

        'Pause for the user
        Console.Read()
    End Sub

    Private Function SortNamesMethod(ByVal name1 As String, _
	ByVal name2 As String) As Integer
        Return name1.CompareTo(name2)
    End Function

    <extension()> _
    Private Sub ToConsole(ByVal items As IEnumerable(Of String), _
                          ByVal title As String)
        If items IsNot Nothing Then
            Console.WriteLine(title)
            For Each item In items
                Console.WriteLine(item)
            Next
            Console.WriteLine("")
        End If
    End Sub
End Module

Typically an extension method is most useful in cases where you'd want that method in more than one place. Our example is small and this is not really necessary, but the exercise will help when dealing with some of the built-in extension methods provided for LINQ.

Lambda Expression

Next, let's get rid of the SortNamesMethod. In a small application like this one, defining a function that is used only once is not a problem, but in a very large application, these kinds of extra functions get to be annoying and confusing. We will use a simple lambda expression instead of the function.

The List(Of String).Sort() function accepts a reference to a function as a parameter. The function doesn't care where the function exists or how it was declared as long as the function takes two strings as parameters and returns an integer indicating if the first string is greater than, equal to or less than the second string. We can replace the AddressOf SortNamesMethod with the lambda expression.

VB.NET
Function(name1 As String, name2 As String) name1.CompareTo(name2)

The compiler reads this lambda expression and creates a function for us. The lambda expression explicitly declares its parameters and the compiler is able to detect that the return type is boolean (because name1.CompareTo(name2) returns a boolean). Once the compiler has created the function, it replaces the lambda expression with the address of the compiler-created function. The resulting binary code is pretty much the same, but the benefit is that I no longer need to deal with that extra function floating around in my code.

Getting closer to LINQ

Now that we have seen how to create an extension method and use a lambda expression, let's look at some of the built-in extension methods available to us.

Let's begin with the Where method. For any IEnumberable(Of T), the Where method will return an IEnumerable(Of T) where all items in the list match the predicate provided as a parameter. That's a little confusing, so let's looks at an example. We will be replacing the for loop which filters the names list by the first letter with the Where extension method.

VB.NET
Dim selected_names As New List(Of String)

For Each name As String In names
    If name.StartsWith(letter) Then selected_names.Add(name)
Next

becomes:

VB.NET
Dim selected_names As New List(Of String)

selected_names = names.Where(Function(name As String) name.StartsWith(letter)).ToList()

We've replaced the for-loop with an extension method. For the predicate parameter, we've used a lambda expression that accepts a String parameter and returns a boolean. The lambda is transformed into a function at compile time, and the Where extension method applies the function to each element in our name list at run time. The Where extension method then returns an IEnumerable(Of String) containing all items in our name list where the result of the predicate (StartsWith(letter)) is true. Then we use the ToList() extension method to transform the IEnumerable(Of String) into an IList(Of String) so that we can assign the result back to the selected_names variable.

We can take this a step further by sorting the list as we filter it. Instead of applying the ToList extension method to the result of the Where extension method, let's apply to OrderBy extension method. The OrderBy extension method takes a name from our list as a parameter and returns some key by which to sort the list. In this case, we want to order the list by the name, so all we need to do is return the name that we passed in as a parameter.

VB.NET
selected_names = names.Where(Function(name As String) name.StartsWith(letter)).ToList()

becomes:

VB.NET
selected_names = names.Where(Function(name As String) _
	name.StartsWith(letter)).OrderBy(Function(name As String) name).ToList()

The sort function later in the code is no longer necessary. Our complete code now looks like:

VB.NET
Imports System.Runtime.CompilerServices

Module Module1
    Sub Main()
        Dim alphabet As String() = New String() _
		{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", _
                    "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", _
                    "U", "V", "W", "X", "Y", "Z"}
        Dim names As String() = New String() _
		{"Adam", "Dave", "John", "Alex", "Daryll", "Jacob", _
                   "Christopher", "Bill", "Ronald", "Jeff"}

        For Each letter In alphabet
            Dim selected_names As New List(Of String)

            selected_names = names.Where(Function(name As String) _
	     name.StartsWith(letter)).OrderBy(Function(name As String) name).ToList()

            If selected_names.Count > 0 Then
                selected_names.ToConsole("Names beginning with '" & letter & "'")
            End If
        Next

        'Pause for the user
        Console.Read()
    End Sub

    <extension()> _
    Private Sub ToConsole(ByVal items As IEnumerable(Of String), _
                          ByVal title As String)
        If items IsNot Nothing Then
            Console.WriteLine(title)
            For Each item In items
                Console.WriteLine(item)
            Next
            Console.WriteLine("")
        End If
    End Sub
End Module

LINQ

Now that we've covered extension methods and lambda expressions, we can convert our selected_names filter into a LINQ statement.

VB.NET
selected_names = names.Where(Function(name As String) _
	name.StartsWith(letter)).OrderBy(Function(name As String) name).ToList()

becomes:

VB.NET
selected_names = (From name In names _
                  Where name.StartsWith(letter) _
                  Order By name).ToList()

This is the exact same statement written two ways. As you can see, LINQ is syntactic sugar to make our extension methods look prettier. Not all extension methods can be replaced with LINQ, which is why we must still call the ToList() extension method as we did before. We must also wrap the LINQ in commas so that it is applied to the result of the entire LINQ statement instead of the name variable.

In addition to what we have seen, there is the Select extension method. This method accepts as a parameter a name and returns whatever object you want to return. The result of applying this extension method to a list is a new list of whatever type you choose to return. LINQ even allows us to return an anonymous type (a new type inferred from the expression with whatever properties we specify). Using the Select extension method in LINQ, we can transform our code to look like this:

VB.NET
Imports System.Runtime.CompilerServices

Module Module1
    Sub Main()
        Dim alphabet As String() = New String() _
		{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", _
                   "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", _
                   "U", "V", "W", "X", "Y", "Z"}
        Dim names As String() = New String() _
		{"Adam", "Dave", "John", "Alex", "Daryll", "Jacob", _
                   "Christopher", "Bill", "Ronald", "Jeff"}

        Dim rolodex = From list In (From letter In alphabet _
                                    Select Letter = letter, Entries = _
					(From name In names _
                                               Where name.StartsWith(letter))) _
                      Where list.Entries.Count() > 0

        For Each page In rolodex
            page.Entries.ToConsole("Names beginning with '" & page.Letter & "'")
        Next

        'Pause for the user
        Console.Read()
    End Sub

    <extension()> _
    Private Sub ToConsole(ByVal items As IEnumerable(Of String), _
                          ByVal title As String)
        If items IsNot Nothing Then
            Console.WriteLine(title)
            For Each item In items
                Console.WriteLine(item)
            Next
            Console.WriteLine("")
        End If
    End Sub
End Module

This creates a list of some anonymous type where the Names property of the anonymous type has more than zero items in it. We then take this list and apply the ToConsole extension method to the Names property of each item and pass in a title using the Letter property.

With fewer lines, this code is easier to read and more durable as there are not as many lines of code to break. As you can see, we have also made use of VB's ability to infer the type of a variable from the assignment expression. Our rolodex variable is strongly typed, but since we used an anonymous type, there is no way to declare the type in a typical dim statement. By allowing VB to infer the type, we can sidestep this requirement and still have the Letter and Entries properties appear in the intellisense list (along with all of the advantages of compile-time type checking).

View on

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)