Introduction
This article provides an introduction to 
employing LINQ to Objects queries to support a simple win forms application; the 
article addresses the construction of LINQ to Objects statements and then goes 
on to describe how one might use LINQ to Objects within the context of an actual 
application.
The demonstration project included with the 
article is a simple contact manager which may be used to capture and store 
information about a person's contacts in address book format.  This 
demonstration application uses LINQ to Objects to manage, query, and order the 
list of contacts maintained by the application.  The demonstration application 
also includes a dummy contact file with a collection of test contacts.
![]()
 
Figure 1:  Application Main Form
The application provides the following 
functionality: 
- Create a contact data file.
- Add contacts to the contact data file.
- Remove contacts from the contact data 
file.
- Search for specific contacts by last 
name.
- Create and edit details about the contact. 
- First Name
- Middle Name
- Last Name
- Street
- City
- State
- Zip Code
- Home Phone
- Work Phone
- Cell Phone
- Email Address
 
- Save a contact data file.
- Reopen a contact data file.
- Navigate through all of the contacts in the contact data file.
- View a list of all contacts in the contact data file. 
- Provide a Rolodex function (search by 
starting letter of last name)
Naturally, the approaches used within the 
application are representative of only one way of doing things; as with most 
things in the .NET world, there are several alternatives and you can modify the 
code to work with the data using one of the other alternatives if you prefer to 
do so.
![]()
Figure 2:  Searching for a Contact by Last 
Name
![]()
Figure 3:  Listing All Contacts (Edits to the 
grid are posted immediately to the List)
![]()
Figure 4:  Rolodex Function
LINQ to Objects Statements
This section will discuss some of the common 
techniques used in LINQ to Objects statement construction.  In a nutshell, LINQ 
to Objects provides the developer with the means to conduct queries against an 
in-memory collection of objects.  The techniques used to query against such 
collections of objects are similar to but simpler than the approaches used to 
conduct queries against a relational database using SQL statements.
Anatomy of LINQ to Objects Statements
Example 1 � A Simple Select
This is an example of a very simple LINQ to 
Objects statement:
Public
Sub Example1()
 
        Dim 
tools() As String 
= {"Tablesaw", 
"Bandsaw", "Planer",
"Jointer", 
"Drill", 
                                
"Sander"}
 
        Dim List 
= From t In 
tools _
                       Select 
t
 
        Dim sb
As New 
StringBuilder()
        Dim s
As String
 
        For
Each s In List
            sb.Append(s + Environment.NewLine)
        Next
 
        MessageBox.Show(sb.ToString(),
"Tools")
 
End
Sub
In the example, an array of strings (tools) is used as the collections 
objects to be queries using LINQ to Objects; the LINQ to Objects query is:
        Dim List 
= From t In 
tools _
                      
Select t
In this example, an untyped variable "List" is 
created and all of the items contained in the string array are added to this 
object; the types are inferred (implicitly typed), for example, "t" is a member 
of tools, since it is known that tools is a string array, the framework will 
infer that "t" is also a string.  Of course this is not all that terrific since 
you can just iterate through the array to do essentially the same thing; 
however, you can create more complex queries with LINQ to Objects and then the 
value of the LINQ library becomes more apparent.
If you were to create a project, add this bit 
of code to a method and run it, the results would look like this:
![]()
Figure 5:  Query Results
Example 2 � Select with a Where Clause
The next example shows a LINQ to Objects query 
that incorporates a where clause.  In this example, we start out with a 
collection of birds in the form of a string array; LINQ to Objects is used to 
query this string array to find and return a subset of the array in the form of 
all birds with names beginning with the letter "R". 
Public
Sub Example2()
 
        Dim 
Birds() As String 
= {"Indigo Bunting",
"Rose Breasted Grosbeak", _ 
                                
"Robin", "House 
Finch", "Gold Finch", _
                                
"Ruby Throated Hummingbird", _
                                
"Rufous Hummingbird",
"Downy Woodpecker"}
 
        Dim list 
= From b In 
Birds _
                   Where 
b.StartsWith("R") _
                   
Select b
 
        Dim sb
As New 
StringBuilder()
        Dim s
As String
 
        For
Each s In list
            sb.Append(s + Environment.NewLine)
        Next
 
        MessageBox.Show(sb.ToString(),
"R Birds")
 
End
Sub
If you were to run this query, the results would 
appear as follows (all birds with names beginning with the letter "R" are 
shown):
![]()
Figure 6:  R Birds Query Results
Example 3 � Select with a Where Clause
In a slight variation to the previous query, 
this example looks for an exact match in its where clause:
    Public
Sub Example3()
 
        Dim 
Birds() As String 
= {"Indigo Bunting",
"Rose Breasted Grosbeak", _ 
                                
"Robin", "House 
Finch", "Gold Finch", _
                                
"Ruby Throated Hummingbird", _
                                
"Rufous Hummingbird",
"Downy Woodpecker"}
 
        Dim list 
= From b In 
Birds _
                   Where 
b = "Indigo Bunting" _
                   
Select b
 
        Dim sb
As New 
StringBuilder()
        Dim s
As String
 
        For
Each s In list
            sb.Append(s + Environment.NewLine)
        Next
 
        MessageBox.Show(sb.ToString(),
"Bunting Birds") 
   
End Sub
Running this code will result in the display 
of this message box:
![]()
Figure 7:  Bird Query Results
Example 4 � Generating an Ordered List
In this query, the list of birds is 
alphabetized (using "Order By b Ascending"):
    Public
Sub Example4()
 
        Dim 
Birds() As String 
= {"Indigo Bunting",
"Rose Breasted Grosbeak", _ 
                                
"Robin", "House 
Finch", "Gold Finch", _
                                
"Ruby Throated Hummingbird", _
                                
"Rufous Hummingbird",
"Downy Woodpecker"}
 
        Dim list 
= From b In 
Birds _
                   Order
By b Ascending 
_
                   
Select b
 
        Dim sb
As New 
StringBuilder()
        Dim s
As String
 
        For
Each s In list
            sb.Append(s + Environment.NewLine)
        Next
 
        MessageBox.Show(sb.ToString(),
"Ordered Birds") 
   
End Sub
![]()
Figure 8:  Ordered Bird List Query Results
Example 5 � Working with a Custom Type
In this example, a typed list is created, 
populated, and then queried using LINQ to Objects.
    Public
Sub Example5()
 
        Dim parts 
= New List(Of 
Parts)
 
        Dim p1
As New Parts()
        p1.PartNumber = 1
        p1.PartDescription =
"Cog"
        parts.Add(p1)
 
        Dim p2
As New Parts()
        p2.PartNumber = 2
        p2.PartDescription =
"Widget"
        parts.Add(p2)
 
        Dim p3
As New Parts()
        p3.PartNumber = 3
        p3.PartDescription =
"Gear"
        parts.Add(p3)
 
        Dim p4
As New Parts()
        p4.PartNumber = 4
        p4.PartDescription =
"Tank"
        parts.Add(p4)
 
        Dim p5 =
New Parts()
        p5.PartNumber = 5
        p5.PartDescription =
"Piston"
        parts.Add(p5)
 
        Dim p6
As New Parts()
        p6.PartNumber = 6
        p6.PartDescription =
"Shaft"
        parts.Add(p6)
 
        Dim p7
As New Parts()
        p7.PartNumber = 7
        p7.PartDescription =
"Pulley"
        parts.Add(p7)
 
        Dim p8
As New Parts()
        p8.PartNumber = 8
        p8.PartDescription =
"Sprocket"
        parts.Add(p8)
 
 
        Dim list 
= From p In 
parts _
                       
Order By p.PartNumber
Ascending _
                       
Select p
 
        Dim sb
As New 
StringBuilder()
        Dim pt
As Parts
 
        For
Each pt In 
parts
sb.Append(pt.PartNumber.ToString() +
": " + _ pt.PartDescription.ToString() + _
            Environment.NewLine)
        Next
 
        MessageBox.Show(sb.ToString(),
"Parts List") 
   
End Sub
The purpose the query is merely to sort the 
parts list in order of the part numbers.  The results returned from this method 
are as follows:
![]()
Figure 9:  Ordered Parts List Query
The parts class used in as the type behind the 
parts list is as follows:
Public
Class Parts
 
    Private 
mPartNumber As 
Integer
    Private 
mPartDescription As 
String 
 
    Public
Sub New()
        ' nothing
    End
Sub 
 
    Public
Sub New(ByVal 
partNum As Integer,
ByVal partDesc As
String)
        mPartNumber = partNum
        mPartDescription = partDesc
    End
Sub 
 
    Public
Property PartNumber() 
As Integer
        Get
            Return 
mPartNumber
        End
Get
        Set(ByVal 
value As Integer)
            mPartNumber = value
        End
Set
    End
Property 
 
    Public
Property PartDescription()
As String
        Get
            Return 
mPartDescription
        End
Get
        Set(ByVal 
value As String)
            mPartDescription = value
        End
Set
    End
Property 
End
Class
Example 6 � Searching a Typed List Using 
LINQ to Objects
In this example, a typed list is created (as 
in the previous example), populated, and then queried using LINQ to Objects.  In 
this case, the query includes a where clause that only returns matches were the 
part description begins with the letter "S":
        Dim list 
= From p In 
parts _
                   Where 
p.PartDescription.StartsWith("S") _
                   Order
By p.PartNumber 
Ascending _
                   
Select p
 
        Dim sb
As New 
StringBuilder()
        Dim pt
As Parts
 
        For
Each pt In 
list
            sb.Append(pt.PartNumber.ToString() +
": " _
            + pt.PartDescription.ToString() + _
            Environment.NewLine)
       
Next
        
MessageBox.Show(sb.ToString(), "Parts List")
 
![]()
 
Figure 10:  Matching Parts Query Results
Example 7 � Searching a Typed List Using 
LINQ to Objects and Returning a Single Result
In this example, a typed list is created (as 
in the previous example), populated, and then queried using LINQ to Objects.  In 
this case, returns a single result of type "Parts":
 Dim 
matchingPart = (From m
In list _
                           
Where m.PartNumber.Equals(5) 
_
                           
Select m).Single()
 
        
MessageBox.Show(matchingPart.PartDescription, 
"Matching Part")
The results of this query are shown in the next 
figure.
![]()
Figure 11:  Returning a Single Result
The preceding examples were intended to 
provide a simple overview as to how to conduct some basic queries against 
collections using LINQ to Objects; there are certainly a great number of more 
complex operations that can be executed using similar procedures (grouping, 
joins and selects into a new custom type, etc.).
Getting Started
There is a single solution included with this 
download, the solution contains a Win Forms project called "LinqToObjectsVB"; 
this project contains two forms (the main form (frmContactBook) and a form used 
to display the total list of contacts (frmFullList), a serializable class called 
'Contact' (used to contain contact related data), and a class entitled, 
'Serializer' which contains two static methods used to serialize and deserialize 
the contact data (writing it to and reading it from a file) .  
If you open the attached project into Visual 
Studio 2008; you should see the following in the solution explorer:
![]()
Figure 12:  Solution Explorer
Code:  Contact.vb
The Contact class is the container class used 
to store all of the contact related data used in the application.  Whilst this 
demonstration uses contact data, this could easily be replaced with something 
more useful to you.
The class begins with the normal and default 
imports:
Imports 
System
Imports 
System.Collections.Generic
Imports 
System.Linq
Imports 
System.Text
The next section contains class declaration.  
Note that the class is declared as serializable; the serializable attribute 
indicates that the class can be serialized.
<Serializable()> _
Public
Class Contact
The region defined in the class declares the 
member variables used internally by the class; any member variables exposed 
externally are made accessible through public properties.
#Region
"Member Variables"
 
    Private mId
As System.Guid
    Private 
mFirstName As String
    Private 
mMiddleName As String
    Private 
mLastName As String
    Private 
mStreet As String
    Private mCity
As String
    Private 
mState As String
    Private mZip
As String
    Private 
mEmail As String
    Private 
mHousePhone As String
    Private 
mWorkPhone As String
    Private 
mCellPhone As String
    Private mFax
As String
 
#End
Region
The next region of code in the class contains 
the constructors.  Two constructors are defined; a default constructor that 
creates a new instance of the class and assigns it an internal ID (as a Guid).  
The second constructor accepts an ID as an argument and sets the contact's 
internal ID to that value.
#Region
"Constructor"
 
    Public
Sub New()
        mId = Guid.NewGuid()
    End
Sub
 
 
    Public
Sub New(ByVal 
ID As System.Guid)
        mId = ID
    End
Sub
 
#End
Region
The last bit of the code in this class is 
contained within the properties region; this region contains all of the 
properties defined to access the member variables.  Note that since the ID value 
is always set by the constructor, the property does not provide a public 
interface to set the Guid to a new value.
#Region
"Properties"
 
    Public
Property FirstName() 
As String
        Get
            Return 
mFirstName
        End
Get
        Set(ByVal 
value As String)
            mFirstName = value
        End
Set
    End
Property 
 
    Public
Property MiddleName() 
As String
        Get
            Return 
mMiddleName
        End
Get
        Set(ByVal 
value As String)
            mMiddleName = value
        End
Set
    End
Property 
 
    Public
Property LastName() 
As String
        Get
            Return 
mLastName
        End
Get
        Set(ByVal 
value As String)
            mLastName = value
        End
Set
    End
Property 
 
    Public
Property Street() As
String
        Get
            Return 
mStreet
        End
Get
        Set(ByVal 
value As String)
            mStreet = value
        End
Set
    End
Property 
 
    Public
Property City() As
String
        Get
            Return 
mCity
        End
Get
        Set(ByVal 
value As String)
            mCity = value
        End
Set
    End
Property 
 
    Public
Property State() As
String
        Get
            Return 
mState
        End
Get
        Set(ByVal 
value As String)
            mState = value
        End
Set
    End
Property 
 
    Public
Property ZipCode() As
String
        Get
            Return 
mZip
        End
Get
        Set(ByVal 
value As String)
            mZip = value
        End
Set
    End
Property 
 
    Public
Property Email() As
String
        Get
            Return 
mEmail
        End
Get
        Set(ByVal 
value As String)
            mEmail = value
        End
Set
    End
Property 
 
    Public
Property HousePhone() 
As String
        Get
            Return 
mHousePhone
        End
Get
        Set(ByVal 
value As String)
            mHousePhone = value
        End
Set
    End
Property 
 
    Public
Property WorkPhone() 
As String
        Get
            Return 
mWorkPhone
        End
Get
        Set(ByVal 
value As String)
            mWorkPhone = value
        End
Set
    End
Property 
 
    Public
Property CellPhone() 
As String
        Get
            Return 
mCellPhone
        End
Get
        Set(ByVal 
value As String)
            mCellPhone = value
        End
Set
    End
Property 
 
    Public
Property Fax() As
String
        Get
            Return 
mFax
        End
Get
        Set(ByVal 
value As String)
            mFax = value
        End
Set
    End
Property 
 
#End
Region 
End
Class
That concludes the description of the 
'Contact' class.
Code:  Main Application Form 
(frmContactBook.vb)
The is the main form of the application; much 
of the code provides the framework for the application and does not really 
pertain to LINQ to Objects, however, all of the code will be described herein to 
provide a proper context.
The contact application's main form contains 
the following controls: 
- Menu
- File
- Contacts
- Add Contact
- Remove Contact
- List All Contacts
 
 
- Toolbar
- Add 
- Remove 
- Find  by Last Name
- Save Data
- Navigate to Previous Contact
- Navigate to Next Bird Contact
- Exit Application
 
- Split Container Left Hand Side
- Alphabet List
- Alphabetized Names List
 
- Split Container Right Hand Side
- First name text box control
- Middle name text box control
- Last name text box control
- Street text box control
- City text box control
- State text box control
- Zip code text box control
- Home phone number text box control
- Work phone number text box control
- Cell number text box control
- Fax number text box control
- Email address text box control
![]()
Figure 13:  frmContactBook.vb
The class begins with the normal and default 
imports:
Imports 
System
Imports 
System.Collections.Generic
Imports 
System.ComponentModel
Imports 
System.Data
Imports 
System.Drawing
Imports 
System.Linq
Imports 
System.Text
Imports 
System.Windows.Forms
The next section contains the class declaration.  
Public
Class frmContactBook
The next region defined in the class declares 
the member variables used internally by the class; any member variables exposed 
externally are made accessible through public properties.  The comment adjacent 
to each declaration describes its purpose.
#Region
"Member Variables"
 
    Private 
contacts As New 
List(Of Contact)    
' create a typed list of contacts
    Private 
currentContact As Contact          
' create a single contact instance
    Private 
currentPosition As 
Integer          ' used to hold current 
position
    Private 
currentFilePath As 
String           ' file path to current 
contact file
    Private 
dirtyForm As Boolean               
' keep track of dirty forms
 
#End
Region
The next region of code in the class contains 
the constructor.  Upon initialization, the application creates a new contact 
data list, creates a new contact data object, sets the current position 
indicator to zero, and sets the dirty form Boolean to false.
#Region
"Constructor" 
 
   
''' <summary>
   
''' Constructor - create a new instance of
   
''' the contact list and setup the local
   
''' member variables
   
''' </summary>
   
''' 
<remarks></remarks>
   
Public Sub
New()
 
       
' This call is required by the Windows Form Designer.
        
InitializeComponent()
 
       
' Add any initialization after the 
InitializeComponent() call.
 
        contacts =
New List(Of 
Contact)
        currentContact =
New Contact()
        
contacts.Add(currentContact)
        currentPosition = 0
        dirtyForm =
False
 
   
End Sub 
#End
Region
The next code region is called 'Toolstrip 
Event Handlers'; the first event handler in this region is the click event 
handler for the Add button; this method merely calls the menu control's click 
event handler and the code contained in that event handler adds a new contact to 
the current contact data.
#Region
"Toolstrip Event Handlers" 
 
    ''' 
<summary>
    ''' Add a new 
contact to the current contact list
    ''' 
</summary>
    ''' 
<param name="sender"></param>
    ''' 
<param name="e"></param>
    ''' 
<remarks></remarks>
    Private
Sub tsbAddRecord_Click(ByVal 
sender As System.Object, _
                                  
ByVal e As 
System.EventArgs) _
                                  
Handles tsbAddRecord.Click
 
        Me.addToolStripMenuItem_Click(Me,
New EventArgs()) 
   
End Sub
The next click event handler is used remove 
the current contact from the contact list when the user clicks the toolstrip's 
remove record button; again, this method merely calls the matching menu item 
function.
   
''' <summary>
   
''' Remove the current contact from the contact list
   
''' </summary>
   
''' <param 
name="sender"></param>
   
''' <param 
name="e"></param>
   
''' 
<remarks></remarks>
   
Private Sub 
tsbRemoveRecord_Click(ByVal sender
As System.Object, _
                                      ByVal e
As System.EventArgs) _
                                      Handles 
tsbRemoveRecord.Click
 
       
Me.removeToolStripMenuItem_Click(Me,
New EventArgs())
 
   
End Sub
The next handler is used to search for a 
specific contact using the contact's last name.  The code uses a LINQ to Objects 
query in order to find the first instance of a matching contact with that last 
name.  The handler uses the search term text box control on the toolstrip to 
capture the last name and it uses the search button to execute the search.  The 
code is annotated to describe what is going on in this method.
    ''' 
<summary>
    ''' Find a contact 
by searching the list
    ''' for a matching 
last name
    ''' 
</summary>
    ''' 
<param name="sender"></param>
    ''' 
<param name="e"></param>
    ''' 
<remarks></remarks>
    Private
Sub tsbFindContact_Click(ByVal 
sender As System.Object, _
                                    
ByVal e As 
System.EventArgs) _
                                    
Handles tsbFindContact.Click
 
        ' return if the 
search term was not provided
        If (String.IsNullOrEmpty(tspSearchTerm.Text))
Then
 
            MessageBox.Show("Enter 
a last name in the space proved.", _
                           
"Missing Search Term")
            Return
 
        End
If
 
 
        Try
            ' using 
linq to objects query to get first matching name
            Dim 
foundGuy = _
                        (From 
contact In contacts _
                         
Where contact.LastName = tspSearchTerm.Text _
                         
Select contact).FirstOrDefault()
 
            ' set the 
current contact to the found contact
            currentContact = foundGuy
            currentPosition = 
contacts.IndexOf(currentContact)
 
            ' update 
the display by loading the 
            ' found 
contact
            LoadCurrentContact()
 
            ' clear the 
search term textbox and return
            tspSearchTerm.Text =
String.Empty
            Return
 
        Catch
 
            MessageBox.Show("No 
matches were found", "Search Complete")
 
        End
Try
 
    End
Sub
The next handler saves the current contact 
list, this handler just calls the matching menu click event handler.
    ''' 
<summary>
    ''' Save the 
current contact list
    ''' 
</summary>
    ''' 
<param name="sender"></param>
    ''' 
<param name="e"></param>
    ''' 
<remarks></remarks>
    Private
Sub tsbSave_Click(ByVal 
sender As System.Object, _
                             
ByVal e As 
System.EventArgs) _
                             
Handles tsbSave.Click
 
        Me.saveStripMenuItem_Click(Me,
New EventArgs())
 
    End
Sub
The next handler is used to navigate back one 
contact from the current position of the displayed contact.  If the contact as 
at the lower limit; the button click is ignored.
   
''' <summary>
   
''' Navigate back to the previous record
   
''' if not at the lower limit
   
''' </summary>
   
''' <param 
name="sender"></param>
   
''' <param 
name="e"></param>
   
''' 
<remarks></remarks>
   
Private Sub 
tsbNavBack_Click(ByVal sender
As System.Object, _
               
                  ByVal e
As System.EventArgs) _
                                 Handles 
tsbNavBack.Click
 
       
' capture form changes and plug them
       
' into the current contact before
       
' navigating off the contact
        
SaveCurrentContact()
 
       
' don't exceed the left limit
       
If (currentPosition <> 0)
Then
            currentPosition 
-= 1
            currentContact 
= contacts(currentPosition)
            
LoadCurrentContact()
       
End If
 
   
End Sub
The next handler is used to navigate forward 
one contact from the current position of the displayed contact.  If the contact 
as at the upper limit; the button click is ignored.
    ''' 
<summary>
    ''' Navigate to the 
next record if
    ''' not at the 
upper limit
    ''' 
</summary>
    ''' 
<param name="sender"></param>
    ''' 
<param name="e"></param>
    ''' 
<remarks></remarks>
    Private
Sub tsbNavForward_Click(ByVal 
sender As System.Object, _
                                   
ByVal e As 
System.EventArgs) _
                                    Handles 
tsbNavForward.Click
 
        ' capture form 
changes and plug them
        ' into the 
current contact before
        ' navigating 
off the contact
        SaveCurrentContact()
 
        ' don't exceed 
the right limit
        If 
(currentPosition < contacts.Count - 1) Then
            currentPosition += 1
            currentContact = 
contacts(currentPosition)
            LoadCurrentContact()
        End
If
   
End Sub
The next handler is used to exit the 
application.  This handler merely calls the matching menu item click event 
handler.
   
''' <summary>
   
''' Exit the application
   
''' </summary>
   
''' <param 
name="sender"></param>
   
''' <param 
name="e"></param>
   
''' 
<remarks></remarks>
   
Private Sub 
tsbExit_Click(ByVal sender
As System.Object, _
                              ByVal e
As System.EventArgs) _
                              Handles 
tsbExit.Click
 
       
Me.exitToolStripMenuItem_Click(Me,
New EventArgs())
 
   
End Sub 
 
#End
Region
The next region contains the menu item click 
event handlers.  The next menu item click event handler creates a new contact 
list; before following through with the creation of the new contact list, this 
handler checks to see if the current form is dirty to allow the user the 
opportunity to save before closing the current list.  Following that, the 
contact list is replaced with a new contact list and the form's controls are 
cleared.
#Region
"Menu Item Click Event Handler" 
 
    ''' 
<summary>
    ''' Create a new 
contact list and clear
    ''' the contact 
form
    ''' 
</summary>
    ''' 
<param name="sender"></param>
    ''' 
<param name="e"></param>
    ''' 
<remarks></remarks>
    Private
Sub newToolStripMenuItem_Click(ByVal 
sender As System.Object, _
                                           ByVal 
e As System.EventArgs) _
                                          
Handles newToolStripMenuItem.Click
 
        ' check to see 
if the form has been editted
        If 
dirtyForm = True Then
 
          If 
(MessageBox.Show(Me,
"You have not saved the current contact data; " 
+ _
                            
"would you like to save before starting a new " 
+ _
                            
"contact database?",
"Save Current Data",  
                MessageBoxButtons.YesNo) = _
                
System.Windows.Forms.DialogResult.Yes) Then
 
                ' 
display the save dialog if the contact list is dirty
                saveAsMenuItem_Click(Me,
New EventArgs())
 
            End
If
 
        Else
 
            ' discard 
the contact list and 
            ' start new 
document
            contacts = 
New List(Of Contact)
            ClearScreen()
 
        End
If 
   
End Sub       
The next event handler is used to open a 
contacts file.  Again, the handler checks to a dirty form and provides the user 
with an opportunity to save if the form is dirty.  A separate open method is 
called to handle the actually file opening operation.
    ''' 
<summary>
    ''' Open an 
existing contact file
    ''' 
</summary>
    ''' 
<param name="sender"></param>
    ''' 
<param name="e"></param>
    ''' 
<remarks></remarks>
    Private
Sub openToolStripMenuItem_Click(ByVal 
sender As System.Object, _
                                           
ByVal e As 
System.EventArgs) _
                                            Handles 
openToolStripMenuItem.Click
 
        ' give the user 
an opportunity to save the current
        ' contact list 
if the data has been edited
        If 
dirtyForm = True Then
 
          If 
(MessageBox.Show(Me,
"You have not saved the current contact data; " 
+ _
                            
"would you like to save before opening a different 
" + _
                            
"contact database?",
"Save Current Data", 
                MessageBoxButtons.YesNo) = _
                
System.Windows.Forms.DialogResult.Yes) Then
 
                saveAsMenuItem_Click(Me,
New EventArgs())
 
            End
If
 
        Else
 
            ' call the 
open function to open the file
            Open()
 
        End
If 
   
End Sub               
The save menu item is used to save the current 
contacts file to disk; the function first calls a "SaveCurrentContact" which is 
used to save the current contact to the current contact data list.  Next, the 
function uses the save file dialog to capture a file name if none is currently 
set to the "currentFilePath" variable, or, if the variable is set, it saves the 
file using that file path.  The file is actually saved to disk when the call to 
serialize the file is made.
    ''' 
<summary>
    ''' Save the 
current contact list
    ''' 
</summary>
    ''' 
<param name="sender"></param>
    ''' 
<param name="e"></param>
    ''' 
<remarks></remarks>
    Private
Sub saveStripMenuItem_Click(ByVal 
sender As System.Object, _
                                       
ByVal e As 
System.EventArgs) _
                                       
Handles saveStripMenuItem.Click
 
        ' save the 
current form data to the list
        SaveCurrentContact()
 
        ' if the file 
path is not set, open the
        ' save file 
dialog
        If
String.IsNullOrEmpty(currentFilePath)
Then
 
            Dim 
SaveFileDialog1 As 
New SaveFileDialog()
 
            Try
                SaveFileDialog1.Title =
"Save CON Document"
                SaveFileDialog1.Filter =
"CON Documents (*.con)|*.con"
 
                If 
(SaveFileDialog1.ShowDialog() = _
                    
System.Windows.Forms.DialogResult.Cancel) Then
                    
Return
                End
If
            Catch
                Return
            End
Try
 
            currentFilePath = 
SaveFileDialog1.FileName
 
        End
If
 
        ' make sure the 
file path is not empty
        If
String.IsNullOrEmpty(currentFilePath)
Then
            Return
        End
If
 
        ' persist the 
contacts file to disk
        Serializer.Serialize(currentFilePath, 
contacts)
 
        ' tell the user 
the file was saved
        MessageBox.Show("File 
" + currentFilePath + " saved.",
"File Saved.")
 
        ' everything is 
saved, set the dirtyform
        ' boolean to 
false
        dirtyForm = 
False 
   
End Sub
The next bit of code us used to support the 
"Save As" menu item; the call is similar to the previous save method but 
straight opens the Save File dialog box to permit the user to name or rename the 
file.
   
''' <summary>
   
''' Save the current contact data as a file
   
''' under a new name
   
''' </summary>
   
''' <param 
name="sender"></param>
   
''' <param 
name="e"></param>
   
''' 
<remarks></remarks>
   
Private Sub 
saveAsMenuItem_Click(ByVal sender
As System.Object, _
                       
              ByVal e 
As System.EventArgs) _
                                     Handles 
saveAsMenuItem.Click
 
       
' save the current form data to the contact list
        
SaveCurrentContact()
 
       
' create and show the save file dialog
       
Dim SaveFileDialog1 
As New SaveFileDialog()
 
       
Try
            
SaveFileDialog1.Title = "Save CON Document As"
            
SaveFileDialog1.Filter = "CON Documents 
(*.con)|*.con"
 
           
If (SaveFileDialog1.ShowDialog() = _
                
System.Windows.Forms.DialogResult.Cancel) Then
               
Return
           
End If
       
Catch
           
Return
       
End Try
 
        currentFilePath = 
SaveFileDialog1.FileName
 
       
' make sure the file path is set
       
If String.IsNullOrEmpty(currentFilePath)
Then
           
Return
       
End If
 
       
' persist the contacts file to disk
        
Serializer.Serialize(currentFilePath, contacts)
 
       
' tell the user the file was saved
        MessageBox.Show("File 
" + currentFilePath + " saved.",
"File Saved.")
 
       
' everything is saved, set the dirtyform
       
' boolean to false
        dirtyForm =
False 
   
End Sub
The next method exits the application but 
checks the dirty form Boolean prior to exiting to give the user a chance to save 
their edits.
    ''' 
<summary>
    ''' Exit the 
application
    ''' 
</summary>
    ''' 
<param name="sender"></param>
    ''' 
<param name="e"></param>
    ''' 
<remarks></remarks>
    Private
Sub exitToolStripMenuItem_Click(ByVal 
sender As System.Object, _
                                           
ByVal e As 
System.EventArgs) _
                                           
Handles exitToolStripMenuItem.Click
 
        If 
dirtyForm = True Then
 
          If 
(MessageBox.Show(Me,
"You have not saved the current contact data; " 
+ _
                            
"would you like to save before exiting?",
"Save Current 
                             Data", 
_
                             
MessageBoxButtons.YesNo) = 
                             
System.Windows.Forms.DialogResult.Yes) Then
 
                tsbSave_Click(Me,
New EventArgs())
 
            End
If
 
        Else
 
            Application.Exit()
 
        End
If 
   
End Sub
The next method is used to add a new contact 
to the current list of contacts; this method saves the current contact to the 
open list of contacts, creates a new contact and adds it to the list of 
contacts, clears the form, and marks the form as dirty:
    ''' 
<summary>
    ''' Add a new 
contact to the current
    ''' contact list
    ''' 
</summary>
    ''' 
<param name="sender"></param>
    ''' 
<param name="e"></param>
    ''' 
<remarks></remarks>
    Private
Sub addToolStripMenuItem_Click(ByVal 
sender As System.Object,
ByVal e As
    System.EventArgs) 
Handles addToolStripMenuItem.Click
 
        SaveCurrentContact()
        currentContact = 
New Contact()
        contacts.Add(currentContact)
        ClearScreen()
        dirtyForm = True 
   
End Sub
The next method removes the current contact 
from the list and updates the display.
    '''
<summary>
   
''' Remove the current contact from the
   
''' contact list and update the display
   
''' </summary>
   
''' <param 
name="sender"></param>
   
''' <param 
name="e"></param>
   
''' 
<remarks></remarks>
   
Private Sub 
removeToolStripMenuItem_Click(ByVal sender
As System.Object, _
                                              ByVal 
e As System.EventArgs) _
                                              Handles 
removeToolStripMenuItem.Click
 
       
' make sure there are records
       
If contacts.Count = 0 
Then
 
           
' remove the current record
            
contacts.Remove(currentContact)
 
           
' check to see if the current
           
' position is at the limit
           
' and move up or down
           
' as required
            If 
(currentPosition = 0) Then
 
                
currentPosition += 1
 
           
End If
 
       
Else
 
            currentPosition 
-= 1
 
           
' reload the current contact
           
' from the new position
            currentContact 
= contacts(currentPosition)
            
LoadCurrentContact()
 
           
' dirty the form since a 
           
' record was removed
            dirtyForm =
True
 
       
End If 
   
End Sub
The next method is used to open a separate 
form displaying all of the contacts in a data grid view control.  This method 
constructs and ordered list and passes it to a new instant of the "frmFullList" 
class which binds the grid control to the list in its constructor.
   
''' <summary>
   
''' Display a list of all the contacts in a data grid
   
''' view control
   
''' </summary>
   
''' <param 
name="sender"></param>
   
''' <param 
name="e"></param>
   
''' 
<remarks></remarks>
   
Private Sub 
listAllContactsToolStripMenuItem_Click(ByVal 
sender As System.Object, 
           
                                            ByVal 
e As System.EventArgs) _
                                                       
Handles 
                                               
listAllContactsToolStripMenuItem.Click
 
       
' use linq to objects to create a list of contacts
       
' ordered by the contact's last name, first name,
       
' and middle name
       
Dim orderedCons = _
            (From 
contact In contacts _
            
Order By 
contact.LastName Ascending, _
             
contact.FirstName Ascending, _
             
contact.MiddleName Ascending _
            
Select contact)
 
       
' create an instance of the full list form and pass 
it's
       
' constructor the list converted to a List
       
Dim f As
New frmFullList(orderedCons.ToList())
        f.Show()
 
   
End Sub 
#End
Region
The next region contains a garbage can 
collection of other methods maintained in a region entitled "Housekeeping":
#Region
"Housekeeping"
The first method contained in this section is 
used to clear all of the text boxes used to display contact information.  This 
is called anytime the current contact is changed to prevent remnants of one 
contact appearing the display of a replacement contact.
   
''' <summary>
   
''' Clear all of the fields in the form
   
''' </summary>
   
''' 
<remarks></remarks>
   
Private Sub 
ClearScreen()
 
        txtFirstName.Text =
String.Empty
        txtMiddleName.Text 
= String.Empty
        txtLastName.Text =
String.Empty
        txtStreet.Text =
String.Empty
        txtCity.Text =
String.Empty
        txtState.Text =
String.Empty
        txtZipCode.Text =
String.Empty
        txtHousePhone.Text 
= String.Empty
        txtWorkPhone.Text =
String.Empty
        txtCellPhone.Text =
String.Empty
        txtFax.Text =
String.Empty
        
txtEmailAddress.Text = String.Empty
 
        tslViewWho.Text =
String.Empty
 
   
End Sub
The next method is used to load the 
information contained in the current contact into the controls used to display 
contact information.
    ''' 
<summary>
    ''' Load the 
current contact into the form fields
    ''' 
</summary>
    ''' 
<remarks></remarks>
    Private
Sub LoadCurrentContact()
 
        ' update the 
form fields
        txtFirstName.Text = 
currentContact.FirstName
        txtMiddleName.Text = 
currentContact.MiddleName
        txtLastName.Text = currentContact.LastName
        txtStreet.Text = currentContact.Street
        txtCity.Text = currentContact.City
        txtState.Text = currentContact.State
        txtZipCode.Text = currentContact.ZipCode
        txtHousePhone.Text = 
currentContact.HousePhone
        txtWorkPhone.Text = 
currentContact.WorkPhone
        txtCellPhone.Text = 
currentContact.CellPhone
        txtFax.Text = currentContact.Fax
        txtEmailAddress.Text = 
currentContact.Email
 
        ' display the 
current user in the status bar
        tslViewWho.Text =
"Now Viewing " + txtFirstName.Text +
" " + txtLastName.Text 
   
End Sub
The next method captures all of the information currently on the form for the 
current contact and writes it into the current contact's properties.  This is 
called whenever a contact is changed so that all edits to an existing contact 
are held within the local list until it can be written to disk.  The method 
further updates the order of the contacts and updates the contact list and 
displayed contact.
    ''' 
<summary>
    ''' Save the form 
data to the current
    ''' contact, 
reorder the contact list,
    ''' and update the 
display
    ''' 
</summary>
    ''' 
<remarks></remarks>
    Private
Sub SaveCurrentContact()
 
        If (Not
String.IsNullOrEmpty(txtFirstName.Text)
And _
            (Not
String.IsNullOrEmpty(txtLastName.Text)))
Then
 
            Try
 
                ' get 
all of the textbox values and
                ' plug 
them into the current contact object
                currentContact.FirstName = 
txtFirstName.Text
                currentContact.MiddleName = 
txtMiddleName.Text
                currentContact.LastName = 
txtLastName.Text
                currentContact.Street = 
txtStreet.Text
                currentContact.City = txtCity.Text
                currentContact.State = 
txtState.Text
                currentContact.ZipCode = 
txtZipCode.Text
                currentContact.HousePhone = 
txtHousePhone.Text
                currentContact.WorkPhone = 
txtWorkPhone.Text
                currentContact.CellPhone = 
txtCellPhone.Text
                currentContact.Fax = txtFax.Text
                currentContact.Email = 
txtEmailAddress.Text
 
 
                ' 
reorder the contacts by last, first, and
                ' 
middle name to keep everything in correct
                ' 
alphabetical order
                Dim 
orderedContacts = _
                        (From 
contact In contacts _
                         
Order By contact.LastName
Ascending, _
                         contact.FirstName
Ascending, _
                         contact.MiddleName
Ascending _
                         
Select contact).ToList()
 
                ' set 
the contacts list to the newly
                ' 
ordered list
                contacts = orderedContacts
 
                ' 
update the current position index value
                currentPosition = 
contacts.IndexOf(currentContact)
 
                ' 
reload the current contact
                LoadCurrentContact()
 
            Catch 
ex As Exception
 
                MessageBox.Show(ex.Message,
"Error")
 
            End
Try
 
        End
If 
   
End Sub
The next method is used to open and 
deserialize an existing contact file, making it available for edit and viewing 
within the application.
   
''' <summary>
   
''' Open an existing contacts file
   
''' </summary>
   
''' 
<remarks></remarks>
   
Public Sub 
Open()
 
       
Dim OpenFileDialog1 
As New OpenFileDialog()
        
OpenFileDialog1.Title = "Open con Document"
        
OpenFileDialog1.Filter = "CON Documents 
(*.con)|*.con"
 
       
If OpenFileDialog1.ShowDialog() = 
System.Windows.Forms.DialogResult.Cancel 
       
Then
           
Return
       
End If
 
        currentFilePath = 
OpenFileDialog1.FileName
 
       
If String.IsNullOrEmpty(currentFilePath)
Then
           
Return
       
End If
 
       
If System.IO.File.Exists(currentFilePath) =
False Then
           
Return
       
End If
 
       
' deserialize file content into contacts 
       
' list to make it available to the application
        contacts = 
Serializer.Deserialize(currentFilePath)
 
       
' alphabetize the contact list
       
' by last, first, and middle name and
       
' push the results into a List
       
Dim orderedContacts = _
                    (From 
contact In contacts _
                    
Order By 
contact.LastName Ascending, _
                      
contact.FirstName Ascending, _
                      
contact.MiddleName Ascending _
                    
Select contact).ToList()
 
       
' set the contacts list to the newly
       
' ordered list
        contacts = 
orderedContacts
 
       
' Load contacts at position zero
       
' if contacts list is not empty
       
If contacts.Count > 0 
Then
            currentContact 
= contacts.ElementAt(0)
            LoadCurrentContact()
            dirtyForm =
False
       
End If 
 
   
End Sub 
#End
Region
The final region in this form class is used to handle the listbox control 
events.  These controls are used to provide a Rolodex sort of functionality to 
the application.  The listbox controls are loaded into the left hand split 
panel's panel.  The top listbox control displays all of the letters in the 
alphabet whilst the lower listbox control is used to display all matching last 
names beginning with the letter selected in the upper listbox.
#Region
"Listbox Event Handlers"
 
The first function handles the selected index changed event for the upper 
listbox containing all of the letters of the alphabet.  When a new letter is 
selected, this method uses a simple LINQ to Objects query to find all contacts 
with last names beginning with the selected letter.  The lower listbox is then 
cleared and then the matches are then formatted into a string showing the 
contact's last name, first name, and middle name and each formatted string is 
then added to the lower listbox control.
 
    ''' 
<summary>
    ''' Find all last 
names starting with the selected letter
    ''' and display 
that list of matching names in the names
    ''' list box
    ''' 
</summary>
    ''' 
<param name="sender"></param>
    ''' 
<param name="e"></param>
    ''' 
<remarks></remarks>
    Private
Sub lstAlphas_SelectedIndexChanged(ByVal 
sender As System.Object, _
                                              
ByVal e As 
System.EventArgs) _
                                               Handles 
lstAlphas.SelectedIndexChanged
 
        ' store the 
selected letter as a local variable
        Dim alpha
As String = 
lstAlphas.SelectedItem.ToString()
 
        ' make sure the 
contact list is not empty
        If 
contacts.Count > 0 Then
 
            Try
 
                ' use 
linq to objects query to find
                ' last 
names matching the selected
                ' 
letter of the alphabet
                Dim 
alphaGroup = _
                    From 
contact In contacts _
                    
Where contact.LastName.ToUpper().StartsWith(alpha) _
                    
Select contact
 
                ' clear 
out any names from the
                ' 
existing list
                lstNames.Items.Clear()
 
                ' add 
the short list of matching
                ' names 
to the list box
                Dim 
con As Contact
                For
Each con In 
alphaGroup
                    
lstNames.Items.Add(con.LastName + ", " + _
                        con.FirstName +
" " + con.MiddleName)
                Next
 
                ' if no 
matches were found, tell the user
                ' with 
a note in the box
                If 
(alphaGroup.Count < 1) Then
                    lstNames.Items.Clear()
                    lstNames.Items.Add("No 
matches were found")
                End
If
 
            Catch
                lstNames.Items.Clear()
                lstNames.Items.Add("No 
matches were found")
            End
Try
 
        End
If 
   
End Sub
Then the names listbox selected index changed 
event is handled in the next block of code.  In it, the name string (Last name, 
first name, middle name) is parsed and used in a LINQ to Objects query used to 
return a list of all matching names;  the first found name is displayed in the 
contact form and the index position is updated to support the list navigation.
    ''' 
<summary>
    ''' Display the 
selected contact's information in the
    ''' contact form's 
fields
    ''' 
</summary>
    ''' 
<param name="sender"></param>
    ''' 
<param name="e"></param>
    ''' 
<remarks></remarks>
    Private
Sub lstNames_SelectedIndexChanged(ByVal 
sender As System.Object, _
                                                 
ByVal e As 
System.EventArgs) _
                                                  Handles 
lstNames.SelectedIndexChanged
 
        ' if there were 
no matches found, return from this function
        If (lstNames.SelectedItem.ToString().Trim() 
= "No matches were found")
Then
            Return
        End
If
 
        ' variables to 
hold parts of the name as search terms
        Dim first
As String =
String.Empty
        Dim 
middle As String 
= String.Empty
        Dim last
As String =
String.Empty
 
        ' get the last 
name
        Dim arr()
As String = 
lstNames.SelectedItem.ToString().Trim().Split(",")
        last = arr(0).Trim()
 
        ' get the first 
name
        Dim 
arr2() As String 
= arr(1).ToString().Trim().Split(" ")
        first = arr2(0).Trim()
 
        ' get the 
middle name
        Try
            middle = arr2(1).Trim()
        Catch
            ' no middle 
name
        End
Try
 
        Try
            ' using 
linq to objects query to get a collection of matching names
            ' when all 
three names match
            Dim 
foundGuy = _
                        (From 
contact In contacts _
                         
Where contact.FirstName.Equals(first)
And _
                               contact.LastName.Equals(last)
And _
                               contact.MiddleName.Equals(middle) 
_
                         
Select contact).FirstOrDefault()
 
            ' set the 
current contact to the first found
            ' contact
            currentContact = foundGuy
 
            ' update 
the index position used to maintain
            ' the 
current position within the list
            currentPosition = 
contacts.IndexOf(currentContact)
 
            ' reload 
the current contact and return
            LoadCurrentContact()
            Return
 
        Catch ex
As Exception
            MessageBox.Show(ex.Message,
"Error Encountered")
        End
Try
 
    End
Sub 
 
#End
Region 
 
End
Class
Code:  frmFullList.vb
This form class contains a data grid view 
control and a constructor which accepts a contact list (List(Of Contact)) as an 
argument.  Upon initialization the list is bound to the data grid view control.
Changes made by edits in the grid are maintained in the contact list.
There is not much code; it is presented here 
in its entirety:
Imports 
System
Imports 
System.Collections.Generic
Imports 
System.ComponentModel
Imports 
System.Data
Imports 
System.Drawing
Imports 
System.Linq
Imports 
System.Text
Imports 
System.Windows.Forms 
 
Public
Class frmFullList
 
    Public
Sub New(ByVal 
cons As List(Of 
Contact))
 
        ' This call is 
required by the Windows Form Designer.
        InitializeComponent()
 
        ' display the 
contact list in the
        ' data grid 
view control
        dgvFullList.DataSource = cons
 
     End
Sub 
End
Class
Summary
The article shows some simple examples of LINQ 
to Objects queries used in support of a sample application.  LINQ to Objects may 
be used to generate more complex queries than are shown in the example, however, 
those demonstrated herein are representative of some of the more common tasks 
that one might choose to do within a similar application.  Much of the code in 
the demonstration project was provided as a framework for the application and 
was necessary to create an environment useful for testing some simple LINQ to 
Objects based queries.