August 23rd, 2007
How often do you write code to manually loop through a generic list or an array using a For…Each type loop? Have you written looping code to search for a particular property of a specific object in a list? Have you wondered if there was an easier way to select a sub-list from a generic list? Many programmers will use the manual loop method to achieve these goals. However, the Action and Predicate delegates offer you an easier and more flexible way to perform these operations.
An Action is a delegate routine that works with a specified object. An Action doesn’t have a return value so it’s always declared as a Sub with a single parameter of a particular type. Pretty simple, but it may be a little confusing at first glance so we’ll work through a simple example.
We start with a generic list of products that have been passed into a routine. We need to load all of the products with a subcode specified by the user into a Listbox. Our ‘old’ way of doing this would look like this:
Public Sub LoadProductList(ByVal products As List(Of InventoryItem)) lstProducts.Items.Clear() For Each item As InventoryItem In products If cboProductSubCode.Text = item.SubCode Then lstProducts.Items.Add(item) End If Next End Sub
In the example above we manually loop through the List and load the Listbox. Now, here’s the code using an Action Delegate:
Public Sub LoadProductListBox(ByVal products As List(Of InventoryItem)) lstProducts.Items.Clear() products.ForEach(AddressOf LoadListBoxItems) End Sub Public Sub LoadListBoxItems(ByVal item As InventoryItem) If cboProductSubCode.Text = item.SubCode Then lstProducts.Items.Add(item) End If End Sub
On the surface, it doesn’t seem like that much of a savings. We now have two routines instead of one, slightly larger, routine. This could impact readability of the code in some cases although it might help with more complex routines or if multiple ForEach calls were required. If we look at the performance, the Action method is slightly faster, by a factor of about 3 to 5 percent, so there is a tiny advantage there.
Where the real power of this method comes into play is where you need to pass a particular action into the routine depending upon other conditions. For example, we might want to have a different action for different types of inventory so we would pass our preferred loading routine as an Action as seen here:
...... If OnlyNewProducts Then LoadProductListBox(InventoryList, New Action(Of InventoryItem)(AddressOf LoadImportedProducts)) Else LoadProductListBox(InventoryList, New Action(Of InventoryItem)(AddressOf LoadAllProducts)) End If ...... Public Sub LoadProductListBox(ByVal products As List(Of InventoryItem), ByVal loadingAction As Action(Of InventoryItem)) lstProducts.Items.Clear() products.ForEach(loadingAction) End Sub
As you might guess, being able to pass in a delegate Action opens up a lot of possibilities.
A Predicate is a delegate function that determines if a specified object meets a set of criteria. Like Action it has one argument, the particular type to be evaluated, and it also returns a Boolean, indicating if the object met or did not meet the selection conditions in the routine. Predicates are used in the Find functions of generic Lists. Let’s start by defining a set of criteria we want our InventoryItem objects to meet. In this case, we’re wanting products added in the past 30 days.
Function SearchForNewProducts(ByVal item As InventoryItem) As Boolean If DateDiff(DateInterval.Day, item.IntroductionDate, Now) < 30 Then Return True Else Return False End If End Function
Now we use the FindAll method to get a list of inventory items using this Predicate.
Dim NewProducts As List(Of InventoryItem) = ProductList.FindAll(AddressOf SearchForNewProducts) . . .
Another operation you might want to do is to see if a member of a list has a property that meets a unique criteria or you may want to just return the first or last matching item in the list. For that, you use the Find or FindLast functions. For our example, we’re going to find the first item that wildcard matches a user TextBox entry.
..... Dim SelectedItem As InventoryItem = NewProducts.Find(AddressOf FindByDescription) ..... Function FindByDescription(ByVal item As InventoryItem) As Boolean If item.Description Like (txtItemDescription.Text & "*") Then Return True Else Return False End If End Function
The other two Find methods are FindIndex and FindLastIndex. They return the index position of the object rather than the object itself. They also have overloads where you can specify the starting position and the number of objects in the list to search. This can be useful for incremental searches of large lists.
Another nice thing about using Predicates is that we can use the same delegate function for all of the Find functions:
..... Dim SelectedItem As InventoryItem = NewProducts.Find(AddressOf FindByDescription) ..... Dim SelectedItem As InventoryItem = NewProducts.FindLast(AddressOf FindByDescription) ..... Dim SelectedItem As List(Of InventoryItem) = NewProducts.FindAll(AddressOf FindByDescription) ..... Dim ItemIndex As Integer = ProductList.FindIndex(AddressOf FindByDescription) ..... Dim ItemIndex As Integer = ProductList.FindLastIndex(AddressOf FindByDescription)
As you can see, this can provide a lot of flexibility in your code.
I hope this article has helped you gain a better understanding of these powerful delegate methods. If you have any questions about them or anything you would like to add, please leave a comment.
Entry Filed under: VB.NET Tutorials
Rate This Article: