This section focuses on querying collections of objects. As you can guess, the most interesting application of LINQ to Objects is to select items from a collection of custom objects. Let’s create a custom class to represent products:
Public Class Product
Private _productID As String
Private _productName As String
Private _productPrice As Decimal
Private _productExpDate As Date
Public Property ProductID() As String
End Property
Public Property ProductName() As String
End Property
Public Property ProductPrice() As Decimal
End Property
Public Property ProductExpDate() As Date
End Property
Code language: JavaScript (javascript)
I’m not showing the implementation of various properties, because they’re quite trivial (nothing more than the default setters and getters). The Products collection is a List object that contains several instances of the class, and it’s populated with statements like the following:
Dim Products As New System.Collections.Generic.List(Of Product)
Dim P As Product
P = New Product
P.ProductID = "10A-Y"
P.ProductName = "Product 1"
P.ProductPrice = 21.45
P.ProductExpDate = #8/1/2009#
Products.add(P)
Code language: PHP (php)
Now we can use LINQ to query our collection of products based on any property (or combination of properties) of its items. To find out the products that cost more than $20 and have expired already, we can formulate the following query:
Dim query = From prod In products _
Where prod.ProductPrice < 20
And Year(prod.ProductExpDate) < 2008 _
Select prod
The result of the query is also a List collection, and it contains the products that meet the specified criteria. To iterate through the selected items and display them in a TextBox control, we use a For. . .Each loop, as shown next:
For Each P In query
TextBox1.AppendText(P.ProductID & vbTab & _
P.ProductName & vbTab & _
P.ProductPrice.ToString("##0.00") & vbTab & _
P.ProductExpDate.ToShortDateString & vbCrLf)
Next
Code language: CSS (css)
Okay, if we have to write the loop, why not examine each item in the loop’s body and not bother with an embedded query? For starters, the same query will work with other data sources. You can replace the collection with an XML document, and the same query will work. Moreover, displaying the data manually is not your only option. In Chapter, ‘‘Building Data-Bound Applications,’’ you’ll learn about DataGridView, which is a data-bound control. You set the control’s DataSource property to a collection of data, and the control displays all the data in its data source in a tabular format. The items of the collection are mapped to rows of the control, and the properties of the objects stored in the collection are mapped to columns, as shown in the grid of Figure 13.1. This is the frmLINQBasics form of the VBLINQ project. In Chapter “Building Data-Bound Applications”, you’ll learn how to customize the appearance of the DataGridView control as well.
The DataGridView control not only is a highly customizable control for browsing sets of data, but also allows the editing of its contents. You can edit the selected items on the control and then access them from within your code through the control’s DataSource property. First, you must cast the control’s DataSource property to a BindingSource object and then access the rows of the control. Each row must be cast in turn into the Product type. The following loop displays the ProductName field of the first row on the control:
Dim prods = CType(DataGridView1.DataSource, BindingSource)
Debug.WriteLine(CType(prods(0), Product).ProductName)
The DataGridView control is a flexible tool for browsing and editing sets of data. You’ll see in Chapter 23 how to bind the DataGridView control to data; in this example, I wanted to show only that you can bind it to any collection.
Another component of a LINQ expression is the Order By clause, which determines how the objects will be ordered in the output list. To sort the output of the preceding example in descending order, append the following Order By clause to the expression:
Dim query = From prod In products _
Where prod.ProductPrice < 20 _
And Year(prod.ProductExpDate) < 2010 _
Select prod _
Order By prod.ProductName
Querying Collections
Dim files = Directory.GetFiles("C:\")
I’m assuming that you have turned on type inference for this project (it’s on by default), so I’m not declaring the type of the files collection. If you hover the pointer over the files keyword, you’ll see that its type is String()—an array of strings. This is the GetFiles method’s return type, so we need not declare the files variable with the same type. The variable’s type is inferred from its value.
The GetFiles method returns an array of strings. To find out the properties of each file, you must create a new FileInfo object for each file and then examine the values of the FileInfo object’s properties. To create an instance of the FileInfo class that represents a file, you’d use the following statement:
Dim FI As New FileInfo(file_name)
Code language: PHP (php)
(As a reminder, the FileInfo class as well as the Directory class belong to the IO namespace. You must either import the namespace into the current project or prefix the class names with the IO namespace: IO.FileInfo). The value of the FI object must now be used in the Where clause of the expression to specify a filter for the query:
Dim smallFiles = _
From file In Directory.GetFiles("C:\") _
Where New FileInfo(file).Length > 10000 _
Order By file
Select file
The file variable is local to the query, and you cannot access it from the rest of the code. You can actually create a new file variable in the loop that iterates through the selected files, as shown in the following code segment:
For Each file In smallFiles
Debug.WriteLine(file)
Next
Code language: CSS (css)
The selection part of the query is not limited to the same variable as specified in the From clause. To select the name of the qualifying files, instead of their paths, use the following selection clause:
Select New FileInfo(file).Name
Code language: CSS (css)
The smallFiles object should still be an array of strings, right? Not quite. This time, if you hover the pointer over the name of the smallFiles variable, you’ll see that its type is IEnumerable(Of String). And it makes sense, because the result of the query is not of the same type as its source. This time we created a new string for each of the selected items, and so smallFiles is an IEnumerable type. Let’s select each file’s name and size with the following query:
Dim smallFiles = _
From file In Directory.GetFiles("C:\") _
Where New FileInfo(file).Length > 10000 _
Select New FileInfo(file).Name, _
New FileInfo(file).Length
This time, smallFiles is of the IEnumerable(Of ) type. And what exactly is the anonymous type? It’s simply a type with no name. Its structure is known, but there’s no name for this type. Because we have selected the two properties of interest in the query itself (the file’s name and size), we can display them with the following loop:
For Each file In smallFiles
Debug.WriteLine(file.Name & vbTab & _
file.Length.ToString)
Next
Code language: CSS (css)
As soon as you type in the name of the file variable and the following period, you will see the Name and Length properties of the anonymous type in the IntelliSense box. As you can see, the editor created a new type behind the scenes for you that exposes the selected values as properties.
The properties of the new type are named after the items you specified in the Select clause and they have the same type. Because the type has no name, it’s called an anonymous type. What this means, practically, is that you can’t declare a new variable of the same type, except by assigning a value of this type to the variable. You can also control the names of the properties of the anonymous type with the following syntax:
Select New With {.FileName = New FileInfo(file).Name, _
.FileSize = New FileInfo(file).Length}
Code language: PHP (php)
This time we select a new object, which is created on-the-fly and has two properties named FileName and FileSize. The values of the two properties are specified as usual. The new object is still of the anonymous type. To display each selected file’s name and size, modify the For. . .Each loop as follows:
For Each file In smallFiles
Debug.WriteLine(file.FileName & vbTab & _
file.FileSize.ToString)
Next
Code language: CSS (css)
As you can see, LINQ is not a trivial substitute for a loop that examines the properties of the collection’s items; it’s a powerful and expressive syntax for querying data in your code, it creates data types on the fly and exposes them in your code.
You can also limit the selection by applying the Where method directly to the collection:
Dim smallFiles = _
Directory.GetFiles("C:\").Where (Function(file) _
(New FileInfo(file).Length > 10000))
The functions you specify in certain extended methods are called lambda functions, and they’re declared either inline, if they’re single line functions, or as delegates.
Let me explain how the Where clause of the last sample code segment works. The Where clause should be followed by an expression that evaluates to a True/False value, the lambda function. First, you specify the signature of a function; in our case, the function accepts a single argument, which is the current item in the collection. Obviously, the Where clause will be evaluated for each item in the collection, and for each item, the function will accept a different object as argument. In the following section, you’ll see lambda functions that accept two arguments. The name of the argument can be anything; it’s a name that you will use in the definition of the function to access the current collection item. Then comes the definition of the function, which is the expression that compares the current file’s size to 100,000 bytes. If the size exceeds 100,000 bytes, the function will return True — otherwise, False.
In this example, the lambda function is implemented inline. To implement more-complicated logic, you can write a function and pass the address of this function to the Where clause. Let’s consider that the function implementing the filtering is the following:
Private Function IsLargeTIFFFile(ByVal fileName As String) As Boolean
Dim file As FileInfo
file = New FileInfo(fileName)
If file.Length > 100000 And file.Extension.ToUpper = ".TIF" Then
Return True
Else
Return False
End If
End Function
Code language: JavaScript (javascript)
To call this function from within a LINQ expression, use the following syntax:
Dim largeImages = _
Directory.GetFiles("C:\").Where(AddressOf IsLargeTIFFFile)
MsgBox(smallFiles.Count)