Chapter 16. Working with Collections and Iterators

Applications often require working with data. Until now you got information on how to store data within classes and structures, but it is common to need to create groups of data. In .NET development, this is accomplished using special classes known as collections, which enable storing a set of objects within one class. The .NET Framework provides a lot of collections, each one specific to a particular need or scenario. The main distinction is between nongeneric collections and generic collections, but all of them can store a set of objects. Collections are not merely groups of data. They are the backend infrastructure for engines such as the ADO.NET Entity Framework and LINQ, so understanding collections is an important task. At a higher level, collections’ infrastructure is offered by the System.Collections namespace and by some interfaces that bring polymorphism to collections. After you have stored data in collections, it is not uncommon for you to analyze the content of such collections; this task is often performed via (but not limited to) For..Each or For..Next loops. Continuing the goal of helping developers write better and more responsive code, the .NET Framework 4.5 brings to Visual Basic a feature called iterators. Iterators make loops more responsive through a background asynchrony. In this chapter you learn about the .NET Framework’s collections, both nongeneric and generic ones. You also learn how to create and consume custom collections, and you get an overview of concurrent collections. Finally, you learn about iterators in Visual Basic 2012, an important new addition to the language.

Understanding Collections Architecture

Collections are special classes that can store sets of objects; the .NET Framework offers both nongeneric and generic collections. Whatever kind of collection you work on, collections implement some interfaces. The first one is ICollection that derives from IEnumerable and provides both the enumerator (which enables For..Each iterations) and special members, such as Count (which returns the number of items within a collection) and CopyTo (which copies a collection to an array). Collections also implement IList or IDictionary; both inherit from ICollection and expose members that enable adding, editing, and removing items from a collection. The difference is that IDictionary works with key/value pairs instead of single objects as IList. The previously mentioned interfaces are about nongeneric collections. If you work with generic collections, the collections implement the generic counterpart of those interfaces such as ICollection(Of T), IList(Of T), and IDictionary(Of TKey, TValue). It’s important to understand this implementation because this provides similarities between collections so that you can learn members from one collection and be able to reuse them against other kinds of collections.


Use the Object Browser

Remember that the Object Browser tool can provide thorough information on objects’ architecture, meaning that if you don’t remember which members are exposed by a collection, this tool can be your best friend.


Working with Nongeneric Collections

The System.Collections namespace defines several nongeneric collections. The namespace also defines interfaces mentioned in the previous section. Here you learn to work with nongeneric collections so that in the next section you can easily understand why generic ones are more efficient.

The ArrayList Collection

System.Collections.ArrayList is the most common nongeneric collection in the .NET Framework and represents an ordered set of items. By ordered, we mean that you add items to the collection one after the other, which is different from sorting. ArrayList collections can store any .NET type because, at a higher level, it accepts items of type Object. The following code shows how you can create a new ArrayList and can set the number of items it can contain by setting the Capacity property:

Dim mixedCollection As New ArrayList
mixedCollection.Capacity = 10

Adding new items to an ArrayList is a simple task. To do this, you invoke the Add method that receives as an argument the object you want to add, as demonstrated by the following snippet:

mixedCollection.Add(35)
mixedCollection.Add("35")
mixedCollection.Add("Alessandro")
mixedCollection.Add(Date.Today)

The preceding code adds an Integer, two Strings, and a Date. Of course, you can also add composite custom types. Always be careful when using an ArrayLists. The first two items’ value is 35, but the first one is an Integer whereas the second is a String. If you want to compare such items or iterate them, you should explicitly convert from String to Integer, or vice versa. This would require boxing/unboxing operations. You work with collections of a single type, so I suggest you avoid nongeneric collections in favor of generic ones. For example, if you have to work with a set of strings, you should use the List(Of String). The second part of this chapter discusses generic collections.


Why You Should Prefer Generics

The second part of this chapter is about generic collections. You should use this kind of collection for two reasons; the first is that generic collections are strongly typed and prevent the possibility of errors that can be caused when working with items of type Object. The second reason is that each time you add a value type to a nongeneric collection, the type is first subject to boxing (that was discussed in Chapter 4, “Data Types and Expressions”), which can cause performance overhead and does not provide the best object management.


You can also add a block of items in a single invocation using the AddRange method. The following code shows how you can add an array of strings to the existing collection:

Dim anArray() As String = {"First", "Second", "Third"}
mixedCollection.AddRange(anArray)

Add and AddRange add items after the last existing item. You might want to add items at a specified position. This can be accomplished via the Insert and InsertRange methods whose first argument is the position; the second one is the object. The following code demonstrates this:

mixedCollection.Insert(3, "New item")
mixedCollection.InsertRange(3, anArray)

You can remove items from the collection using one of two methods. Remove enables you to remove a specific object, and RemoveAt enables you to remove the object at the specified position:

'Removes the string "35"
mixedCollection.RemoveAt(1)
'Removes 35

mixedCollection.Remove(35)

One interesting method is TrimToSize; it enables you to resize the collection based on the number of items effectively stored. For example, consider the following code:

Dim mixedCollection As New ArrayList
mixedCollection.Capacity = 10

mixedCollection.Add(32)
mixedCollection.Add("32")
mixedCollection.Add("Alessandro")
mixedCollection.Add(Date.Today)

mixedCollection.TrimToSize()

When created, the ArrayList can store up to 10 items. After the TrimToSize invocation, Capacity’s value is 4, which is the number of items effectively stored. If you have a large collection, the ArrayList provides a BinarySearch method that enables searching the collection for a specified item, returning the index of the item itself:

'Returns 2
Dim index As Integer = mixedCollection.BinarySearch("Alessandro")

If the item is not found within the collection, the return value is a negative number. If the collection contains more than one item matching the search criteria, BinarySearch returns only one item—which is not necessarily the first one. This collection also exposes other interesting members that are summarized in Table 16.1.

Table 16.1. ArrayList Members

Image

You can access an item from the ArrayList using the index as follows:

Dim anItem As Object = mixedCollection(0)

You need to perform a conversion into the appropriate type, which is something you can accomplish inline if you already know the type:

Dim anItem As Integer = CInt(mixedCollection(0))

If the conversion fails, an InvalidCastException is thrown. The ArrayList implements the IList interface and thus can take advantage of the enumerator; therefore, you can iterate it via a For..Each loop as follows:

For Each item As Object In mixedCollection
    Console.WriteLine(item.ToString)

Next

You just need to pay attention to the fact that each item is treated as an Object, so you must be aware of conversions. The ArrayList collection provides members that you find in other kinds of collections. This is the reason the most common members are discussed here.

The Queue Collection

The System.Collections.Queue collection works according to the FIFO (First-In, First-Out) paradigm, meaning that the first item you add to the collection is the first pulled out of the collection. Queue exposes two methods: Enqueue adds a new item to the collection, and Dequeue removes an item from the collection. Both methods receive an argument of type Object. The following code provides an example:

Sub QueueDemo()

    Dim q As New Queue
    q.Enqueue(1)
    q.Enqueue(2)

   'Returns
   '1
   '2
    Console.WriteLine(q.Dequeue)
    Console.WriteLine(q.Dequeue)

End Sub

You just need to invoke the Dequeue method to consume and automatically remove an item from the collection. You can also invoke the Peek method, which returns the first item from the collection without removing it. Be careful when adding items to a queue because you are working in a fashion that is not strongly typed. If you plan to work with objects of a specified type (for example, you need a collection of Integer), consider using a Queue(Of T) that behaves the same way except that it is strongly typed. The collection also exposes a Count property that returns the number of items in the collection. The constructor provides an overload that enables specifying the capacity for the collection.

The Stack Collection

The System.Collections.Stack collection mimics the same-named memory area and works according to the LIFO (Last-In, First-Out) paradigm meaning that the last item you add to the collection is the first that is pulled out from the collection. Stack exposes three important methods: Push adds an item to the collection, Pop enables the consuming of and removing of an item from the collection, and Peek returns the top item in the collection without removing it. The following is an example:

Dim s As New Stack

s.Push(1)
s.Push(2)

'Returns 2 and leaves it in the collection
Console.WriteLine(s.Peek)
'Returns 2 and removes it
Console.WriteLine(s.Pop)
'Returns 1 and removes it
Console.WriteLine(s.Pop)

As for Queue, here you work with Object items. Although this enables pushing different kinds of objects to the collection, it is not a good idea. You should use the generic counterpart (Stack(Of T)) that enables you to work with a single type in a strongly typed fashion. The Stack collection also exposes the Count property, which returns the number of objects that it stores.

The HashTable Collection

The System.Collections.HashTable collection can store items according to a key/value pair, where both key and value are of type Object. The HashTable peculiarity is that its items are organized based on the hash code of the key. The following code provides an example:

Dim ht As New Hashtable
ht.Add("Alessandro", "Del Sole")
ht.Add("A string", 35)
ht.Add(3.14, New Person)

'Number of items
Console.WriteLine(ht.Count)
'Removes an item based on the key
ht.Remove("Alessandro")

Items within a HashTable can be accessed by the key, as shown in the last line of code in the preceding example. It also offers two methods named ContainsKey and ContainsValue that check whether a key or a value exists within the collection, as demonstrated here:

'Checks if a key/value exists
Dim checkKey As Boolean = ht.ContainsKey("A string")
Dim checkValue As Boolean = ht.ContainsValue(32)

Note that if you do not check whether a key already exists, as in the preceding example, and attempt to add a key that already exists, then an ArgumentException is thrown. This is because keys must be unique. A single item within the collection is of type DictionaryEntry that exposed two properties, Key and Value. For example, you can iterate a HashTable as follows:

'iterate items
For Each item As DictionaryEntry In ht
    Console.WriteLine("{0} {1}", item.Key, item.Value)
Next

HashTable also offers two other properties named Keys and Values that return an ICollection containing keys in the key/value pair and values in the same pair, respectively. This is demonstrated here:

'iterate keys
For Each key As Object In ht.Keys
    Console.WriteLine(key)
Next

It is recommend that you use a strongly typed Dictionary(Of T, T) that provides more efficiency. (And this suggestion is appropriate when discussing other dictionaries.)

The ListDictionary Collection

The System.Collections.Specialized.ListDictionary collection works exactly like HashTable but differs in that it is more efficient until it stores up to 10 items. It is not preferred after the item count exceeds 10.

The OrderedDictionary Collection

The System.Collections.Specialized.OrderedDictionary collection works like HashTable but differs in that items can be accessed via either the key or the index, as demonstrated by the following code:

Dim od As New OrderedDictionary
od.Add("a", 1)

'Access via index
Dim item As DictionaryEntry = CType(od(0), DictionaryEntry)
Console.WriteLine(item.Value)

The SortedList Collection

The System.Collections.SortedList collection works like HashTable but differs in that items can be accessed via the key or the index items are automatically sorted based on the key. For example, look at the following code:

Dim sl As New SortedList
sl.Add("Del Sole", 2)
sl.Add("Alessandro", 1)

For Each item As String In sl.Keys
    Console.WriteLine(item)
Next

It sorts items based on the key; therefore, it produces the following result:

Alessandro
Del Sole

The HybridDictionary Collection

The System.Collections.Specialized.HybridDictionary collection is a dynamic class in that it implements a ListDictionary until the number of items is small and then switches to HashTable if the number of items grows large. Technically, it works like HashTable.

The StringCollection Collection

The System.Collections.Specialized.StringCollection is similar to the ArrayList collection except that it is limited to accepting only strings. The following code provides an example:

Dim stringDemo As New StringCollection

stringDemo.Add("Alessandro")
stringDemo.Add("Del Sole")

'Returns True
Dim containsString As Boolean = stringDemo.Contains("Del Sole")

stringDemo.Remove("Alessandro")

You can use the same members of ArrayList and perform the same operations.

The StringDictionary Collection

The System.Collections.Specialized.StringDictionary collection works like the HashTable collection but differs in that it accepts only key/value pairs of type String, meaning that both keys and values must be String. The following is a small example:

Dim stringDemo As New StringDictionary

stringDemo.Add("Key1", "Value1")
stringDemo.Add("Alessandro", "Del Sole")

'Simple iteration
For Each value As String In stringDemo.Values
    Console.WriteLine(value)
Next

You can recall the HashTable collection for a full member listing.

The NameValueCollection Collection

The NameValueCollection behaves similarly to the StringDictionary collection, but it differs in that NameValueCollection enables accessing items via either the key or the index. The following code snippet provides a brief demonstration:

Dim nv As New NameValueCollection

nv.Add("First string", "Second string")

Dim item As String = nv(0)

The Add method also provides an overload accepting another NameValueCollection as an argument.

The BitArray Collection

The System.Collections.BitArray collection enables storing bit values represented by Boolean values. A True value indicates that a bit is on (1), whereas a False value indicates that a bit is off (0). You can pass to the BitArray arrays of Integer numbers or Boolean values, as demonstrated by the following code:

Dim byteArray() As Byte = New Byte() {1, 2, 3}
'Length in zero base
Dim ba As New BitArray(byteArray)

For Each item As Object In ba
    Console.WriteLine(item.ToString)
Next

This code produces the output shown in Figure 16.1, which is a human-readable representation of the bits in the collection.

Image

Figure 16.1. Human-readable representation of bits in a BitArray collection.

The constructor also accepts a length argument that enables specifying how large the collection must be.

The Bitvector32 Collection

System.Collections.Specialized.BitVector32 has the same purpose of BitArray but differs in two important elements; the first one is that BitVector32 is a structure that is a value type and therefore can take advantage of a faster memory allocation. On the other hand, the collection manages only 32-bit integers. All data is stored as 32-bit integers that are affected by changes when you edit the collection. The most important (shared) method is CreateMask that enables creating a mask of bits. The method can create an empty mask (which is typically for the first bit) or create subsequent masks pointing to the previous bit. When done, you can set the bit on or off by passing a Boolean value. The following code provides an example:

'Passing zero to the constructor
'ensures that all bits are clear
Dim bv As New BitVector32(0)

Dim bitOne As Integer = BitVector32.CreateMask
Dim bitTwo As Integer = BitVector32.CreateMask(bitOne)
Dim bitThree As Integer = BitVector32.CreateMask(bitTwo)

bv(bitOne) = True
bv(bitTwo) = False
bv(bitThree) = True

The Data property stores the actual value of the collection, as demonstrated here:

'Returns 5 (the first bit + the second bit = the third bit)
Console.WriteLine(bv.Data)

If you instead want to get the binary representation of the data, you can use the name of the instance. The following code demonstrates this:

Console.WriteLine(bv)

This code produces the following result:

BitVector32{00000000000000000000000000000101}


Members and Extension Methods

Collections provide special members, such as properties and methods, and are extended by most of the built-in extension methods. Providing a thorough discussion on each member is not possible; IntelliSense can point you in the right direction to provide explanations for each member. Each member has a self-explanatory identifier; therefore, it is easy to understand what a member does when you understand the high-level logic. In this chapter you are introduced to the most important members from collections so that you can perform the most common operations.


Working with Generic Collections

The .NET Framework offers generic counterparts of the collections described in the previous section. Moreover, it offers new generic collections that are specific to particular technologies such as WPF. In this section, you learn to work with generic built-in collections and how you can take advantage of a strongly typed fashion. Generic collections are exposed by the System.Collections.Generic namespace, but a different namespace is explained.

The List(Of T) Collection

The System.Collections.Generic.List(Of T) collection is a generic ordered list of items. It is a strongly typed collection, meaning that it can accept only members of the specified type. It is useful because it provides support for adding, editing, and removing items within the collection. For example, imagine you need to store a series of Person objects to a collection. This can be accomplished as follows:

Dim person1 As New Person With {.FirstName = "Alessandro",
                                .LastName = "Del Sole",
                                .Age = 35}
Dim person2 As New Person With {.FirstName = "XXXXX",
                                .LastName = "ZZZZZZZZ",
                                .Age = 44}
Dim person3 As New Person With {.FirstName = "YYYYY",
                                .LastName = "DDDDDDDD",
                                .Age = 18}

Dim personList As New List(Of Person)
personList.Add(person1)
personList.Add(person2)
personList.Add(person3)

You notice that the List(Of T) has lots of members in common with its nongeneric counterpart—the ArrayList. This is because the first one implements the IList(Of T) interface, whereas the second one implements IList. The following code shows how you can access an item within the collection using the IndexOf method and how you can remove an item invoking the Remove method, passing the desired instance of the Person class:

'Returns the index for Person2
Dim specificPersonIndex As Integer = personList.IndexOf(person2)

'Removes person3
personList.Remove(person3)

The List(Of T) still provides members such as Capacity, AddRange, Insert, and InsertRange, but it exposes a method named TrimExcess that works like the ArrayList.TrimToSize. Because the List(Of T) implements the IEnumerable(Of T) interface, you can then iterate the collection using a classic For..Each loop, but each item is strongly typed, as demonstrated here:

For Each p As Person In personList
    Console.WriteLine(p.LastName)
Next

If you need to remove all items from a collection, you can invoke the Clear method, and when you do not need the collection anymore, you assign it to Nothing:

personList.Clear()
personList = Nothing

In this case, the code simply assigns Nothing to the list because this does not implement the IDisposable interface. If it did, you have to be sure to invoke the Dispose method before clearing and destroying the collection to avoid locking resources. There are also other interesting ways to interact with a collection, such as special extension methods. Extension methods are discussed in Chapter 20, “Advanced Language Features.” For now, you need to know that they are special methods provided by the IEnumerable(Of T) interface that enables performing particular operations against a collection. For example, the Single method enables retrieving the unique instance of a type that matches the specified criteria:

'Returns a unique Person whose LastName
'property is Del Sole
Dim specificPerson As Person = personList.Single(Function(p) _
                               p.LastName = "Del Sole")

This method receives a lambda expression as an argument that specifies the criteria. Lambda expressions are also discussed in Chapter 20. Another interesting method is FindAll, which enables the generation of a new List(Of T) containing all the type instances that match a particular criteria. The following snippet retrieves all the Person instances whose LastName property starts with the letter D:

'Returns a new List(Of Person) storing
'all Person instances whose LastName starts
'with "D"
Dim specificPeople = personList.FindAll(Function(p) _
                                      p.LastName.StartsWith("D"))

As usual, IntelliSense can be your best friend in situations such as this. Because all members from each collection cannot be described here, that technology can help you understand the meaning and the usage of previously mentioned members whose names are always self-explanatory.


Investigating Collections at Debug Time

Chapter 5, “Debugging Visual Basic 2012 Applications,” discussed the Data Tips features of the Visual Studio debugger. They are useful if you need to investigate the content of collections while debugging, especially if you need to get information on how collections and their items are populated.


Working with Collection Initializers

Visual Basic 2012 implements a language feature known as collection initializers. This feature works like the object initializers, except that it is specific for instantiating and populating collections inline. To take advantage of collection initializers, you need to use the From reserved keyword enclosing items within brackets, as demonstrated in the following code:

'With primitive types
Dim listOfIntegers As New List(Of Integer) From {1, 2, 3, 4}

The preceding code produces the same result as the following:

Dim listOfIntegers As New List(Of Integer)

listOfIntegers.Add(1)
listOfIntegers.Add(2)
listOfIntegers.Add(3)
listOfIntegers.Add(4)

You can easily understand how collection initializers enable writing less code that’s more clear. This feature can also be used with any other .NET type. The following code snippet shows how you can instantiate inline a List(Of Person):

'With custom types
Dim person1 As New Person With {.FirstName = "Alessandro",
                                .LastName = "Del Sole",
                                .Age = 35}
Dim person2 As New Person With {.FirstName = "XXXXX",
                                .LastName = "ZZZZZZZZ",
                                .Age = 44}
Dim person3 As New Person With {.FirstName = "YYYYY",
                                .LastName = "DDDDDDDD",
                                .Age = 18}

Dim people As New List(Of Person) From {person1,
                                        person2,
                                        person3}

The code also shows how you can take advantage of implicit line continuation if you have long lines for initializations. When you have an instance of the new collection, you can normally manipulate it. Of course, this feature works with any other collection type.


Non Generic Collections

Collection initializers are also supported by nongeneric collections using the same syntax.


The ReadOnlyCollection(Of T) Collection

The System.Collections.ObjectModel.ReadOnlyCollection(Of T) is the read-only counterpart of the List(Of T) class. Being read-only, you can add items to the collection only when you create an instance, but then you cannot change it. The constructor requires an argument of type IList(Of T) so that the new collection will be generated starting from an existing one. The following code demonstrates how you can instantiate a new ReadonlyCollection:

Dim person1 As New Person With {.FirstName = "Alessandro",
                                .LastName = "Del Sole",
                                .Age = 35}
Dim person2 As New Person With {.FirstName = "XXXXX",
                                .LastName = "ZZZZZZZZ",
                                .Age = 44}
Dim person3 As New Person With {.FirstName = "YYYYY",
                                .LastName = "DDDDDDDD",
                                .Age = 18}

Dim people As New List(Of Person) From {person1, person2, person3}

Dim readonlyPeople As New ReadOnlyCollection(Of Person)(people)

As an alternative, you can create a ReadonlyCollection invoking the List(Of T).AsReadOnly method, as shown in the following code:

'Same as above
Dim readonly As ReadOnlyCollection(Of Person) = people.AsReadOnly

Invoking AsReadOnly produces the same result of creating an explicit instance.

The Dictionary(Of TKey, TValue) Collection

The System.Collections.Generic.Dictionary(Of TKey, TValue) collection is the generic counterpart for the HashTable. Each item within a Dictionary is a key/value pair; therefore, the constructor requires two arguments (TKey and TValue) where the first one is the key and the second one is the value. The following code shows instantiating a Dictionary(Of String, Integer) in which the String argument contains a person’s name and the Integer argument contains the person’s age:

Dim peopleDictionary As New Dictionary(Of String, Integer)
peopleDictionary.Add("Alessandro", 35)
peopleDictionary.Add("Stephen", 27)

peopleDictionary.Add("Rod", 44)

A single item in the collection is of type KeyValuePair(Of TKey, TValue), and both arguments reflect the collection’s ones. For a better explanation, take a look at the following iteration that performs an action on each KeyValuePair:

For Each item As KeyValuePair(Of String, Integer) In peopleDictionary
    Console.WriteLine(item.Key & " of age " & item.Value.ToString)

Next

The previous code will produce the following output:

Alessandro of age 35
Stephen of age 27
Rod of age 44

Each KeyValuePair object has two properties, Key and Value, which enable separated access to parts composing the object. You can then manipulate the Dictionary like you would other collections.

The SortedDictionary(Of TKey, TValue) Collection

The System.Collections.Generic.SortedDictionary(Of TKey, TValue) works exactly like the Dictionary collection, except that items are automatically sorted each time you perform a modification. You can rewrite the code shown in the section about the Dictionary(Of TKey, TValue) collection as follows:

Dim peopleDictionary As New SortedDictionary(Of String, Integer)

peopleDictionary.Add("Alessandro", 35)
peopleDictionary.Add("Stephen", 27)
peopleDictionary.Add("Rod", 44)

For Each item As KeyValuePair(Of String, Integer) In peopleDictionary
    Console.WriteLine(item.Key & " of age " & item.Value.ToString)

Next

This code will produce the following result:

Alessandro of age 35
Rod of age 44
Stephen of age 27

Notice how the result has a different order than the one we added items to the collection with. In fact, items are sorted alphabetically. This collection performs sorting based on the Key part.

The ObservableCollection(Of T) Collection

The System.Collections.ObjectModel.ObservableCollection(Of T) is a special collection that is typically used in WPF, Silverlight, and Windows Store applications. Its main feature is that it implements the INotifyPropertyChanged interface and can therefore raise an event each time its items are affected by any changes, such as adding, replacing, or removing. Thanks to this mechanism, the ObservableCollection is the most appropriate collection for the WPF and Silverlight data-binding because it provides support for two-way data-binding in which the user interface gets notification of changes on the collection and is automatically refreshed to reflect those changes. Although a practical example of this scenario is offered in the chapters about WPF, this section shows you how the collection works. The ObservableCollection is exposed by the System.Collections.ObjectModel namespace, meaning that you need to add an Imports directive for this namespace. Notice that in the .NET Framework 4.5 the ObservableCollection has been moved to the System.dll assembly from the WindowsBase.dll assembly. This makes it usable not only in WPF applications, but also in other kinds of applications (and this is why you do not need to add any reference manually as you would do in previous versions). Now consider the following code snippet:

Dim people As New ObservableCollection(Of Person)
AddHandler people.CollectionChanged, AddressOf CollectionChangedEventHandler

The people variable represents an instance of the collection, and its purpose is to store a set of Person class instances. Because the collection exposes a CollectionChanged event, which enables intercepting changes to items in the collection, we need an event handler to understand what is happening. The following code shows an example of event handler implementation:

Private Sub CollectionChangedEventHandler(ByVal sender As Object,
                           ByVal e As Specialized.
                           NotifyCollectionChangedEventArgs)

    Select Case e.Action
        Case Is = Specialized.NotifyCollectionChangedAction.Add
            Console.WriteLine("Added the following items:")
            For Each item As Person In e.NewItems
                Console.WriteLine(item.LastName)
            Next
        Case Is = Specialized.NotifyCollectionChangedAction.Remove
            Console.WriteLine("Removed or moved the following items:")
            For Each item As Person In e.OldItems
                Console.WriteLine(item.LastName)
            Next
    End Select
End Sub

The System.Collections.Specialized.NotifyCollectionChangedEventArgs type exposes some interesting properties for investigating changes on the collection. For example, the Action property enables you to understand if an item was added, moved, replaced, or removed by the collection via the NotifyCollectionChangedAction enumeration. Next, the collection exposes other interesting properties such as NewItems, which returns an IList object containing the list of items that were added, and OldItems, which returns an IList object containing the list of items that were removed/moved/replaced. The NewStartingIndex and the OldStartingIndex provide information on the position where changes (adding and removing/replacing/moving, respectively) occurred. Now consider the following code, which declares three new instances of the Person class and then adds them to the collection and finally removes one:

Dim person1 As New Person With {.FirstName = "Alessandro",
                                .LastName = "Del Sole",
                                .Age = 35}
Dim person2 As New Person With {.FirstName = "XXXXX",
                                .LastName = "ZZZZZZZZ",
                                .Age = 44}
Dim person3 As New Person With {.FirstName = "YYYYY",
                                .LastName = "DDDDDDDD",
                                .Age = 18}

people.Add(person1)
people.Add(person2)
people.Add(person3)
people.Remove(person1)

The ObservableCollection works like the List one; therefore, it exposes methods such as Add, Remove, RemoveAt, and so on and supports extension methods. The good news is that each time a new item is added or an item is removed, the CollectionChanged event is raised and subsequently handled. Because of our previous implementation, if you run the code, you get the following output:

Added the following items:
Del Sole
Added the following items:
ZZZZZZZZ
Added the following items:
DDDDDDDD
Removed or moved the following items:
Del Sole

Because of its particularity, the ObservableCollection(Of T) can also be useful in scenarios different from WPF, Silverlight, and WinRT, though they remain the best places where you can use it.

The ReadonlyObservableCollection(Of T) Collection

As for the List(Of T) and also for the ObservableCollection(Of T), there is a read-only counterpart named ReadonlyObservableCollection(Of T) that works like the ReadonlyCollection(Of T) plus the implementation of the CollectionChanged event. The collection is also exposed by the System.Collections.ObjectModel namespace.

The LinkedList(Of T) Collection

Think of the System.Collections.Generic.LinkedList(Of T) collection as a chain in which each ring is an item in the collection that is linked to the others. In other words, an item is linked to the previous one and the next one and points to them. Each item in the collection is considered as a LinkedListNode(Of T), so if you decide to create a LinkedList(Of Person), each Person instance will be represented by a LinkedListNode(Of Person). Table 16.2 summarizes the most common methods and properties for the collection over the ones that you already know (derived from IList(Of T)).

Table 16.2. LinkedList Members

Image

The following code provides an example of creating and consuming a LinkedList(Of Person) collection (see comments for explanations):

Dim person1 As New Person With {.FirstName = "Alessandro",
                                .LastName = "Del Sole",
                                .Age = 35}
Dim person2 As New Person With {.FirstName = "XXXXX",
                                .LastName = "ZZZZZZZZ",
                                .Age = 44}
Dim person3 As New Person With {.FirstName = "YYYYY",
                                .LastName = "DDDDDDDD",
                                .Age = 18}

'Creates a new LinkedList
Dim linkedPeople As New LinkedList(Of Person)

'Creates a series of nodes
Dim node1 As New LinkedListNode(Of Person)(person1)
Dim node2 As New LinkedListNode(Of Person)(person2)
Dim node3 As New LinkedListNode(Of Person)(person3)

'The first item in the collection
linkedPeople.AddFirst(node1)

'The last one
linkedPeople.AddLast(node3)

'Add a new item before the last one and after
'the first one
linkedPeople.AddBefore(node3, node2)

'Removes the last item
linkedPeople.RemoveLast()
'Gets the instance of the last item
'(person2 in this case)
Dim lastPerson As Person = linkedPeople.Last.Value

'Determines if person1 is within the collection
Dim isPerson1Available As Boolean = linkedPeople.Contains(person1)

The most important difference between this collection and the other ones is that items are linked. This is demonstrated by an Enumerator structure exposed by every instance of the collection that enables moving between items, as demonstrated in the following code snippet:

Dim peopleEnumerator As LinkedList(Of Person).
    Enumerator = linkedPeople.GetEnumerator

Do While peopleEnumerator.MoveNext
    'Current is a property that is of type T
    '(Person in this example)
    Console.WriteLine(peopleEnumerator.Current.LastName)
Loop

This code demonstrates that items in the collections are linked and that each one points to the next one (see Figure 16.2).

Image

Figure 16.2. Demonstrating linked items in a LinkedList collection.

The Queue(Of T) and Stack(Of T) Collections

The .NET Framework offers generic versions of the Queue and Stack collections, known as System.Collections.Generic.Queue(Of T) and System.Collections.Generic.Stack(Of T). Their behavior is the same as nongeneric version, except that they are strongly typed. Because of this, you already know how to work with generic versions, so they are not discussed here.

Building Custom Collections

Built-in collections are good for most scenarios. In some situations you might need to implement custom collections. You have two options: creating a collection from scratch or recur to inheritance. The first choice can be hard. You create a class implementing the ICollection(Of T) and IList(Of T) (or IDictionary) interfaces, but you need to manually write code for performing the most basic actions onto items. The other choice is inheriting from an existing collection. This is a good choice for another reason: You can create your custom base class for other collections. Imagine you want to create a custom collection that stores sets of FileInfo objects, each one representing a file on disk. It would not be useful to reinvent the wheel, so inheriting from List(Of T) is a good option. The following code inherits from List(Of FileInfo) and extends the collection implementing a new ToObservableCollection method, which converts the current instance into an ObservableCollection(Of FileInfo) and overrides ToString to return a customized version of the method:

Public Class FileInfoCollection
    Inherits List(Of FileInfo)


    Public Overridable Function ToObservableCollection() As _
           ObservableCollection(Of FileInfo)
        Return New ObservableCollection(Of FileInfo)(Me)
    End Function

    Public Overrides Function ToString() As String
        Dim content As New StringBuilder

        For Each item As FileInfo In Me
            content.Append(item.Name)
        Next
        Return content.ToString
    End Function
End Class

Now you have a strongly typed collection working with FileInfo objects. Plus, you extended the collection with custom members.

Concurrent Collections

The .NET Framework 4.5 includes the Task Parallel Library (TPL), which offers support for multicore CPU architectures. The library exposes specific generic collections, via the System.Collections.Concurrent namespace that was introduced by .NET 4.0. Table 16.3 gives you a list of the new classes.

Table 16.3. Concurrent Collections

Image

You get an overview of these collections in the appropriate chapters, but for completeness, you now have a full list of available collections.

Iterators in Visual Basic

The concept of iterators is not new in modern programming language—for example, Visual C# has had iterators since version 2005. However, version 2012 is the first time this feature becomes part of the Visual Basic programming language. Technically speaking, iterators are used to step through a collection and iterator functions perform custom iterations over a collection. They can have only an IEnumerable, IEnumerable(Of T), IEnumerator, or IEnumerator(Of T) as their return type. Iterators use the new Yield keyword to return the item that is currently iterated to the caller code; then the iteration stops at that point. After Yield executes, the iteration restarts from that point. With this kind of approach, an iteration over a collection is more responsive and makes items in the collection immediately available to the caller code right after they have been returned. If this technical explanation can seem difficult, let’s provide an easier one. Imagine you have a For..Each loop that iterates through a collection and returns a list of elements; such a loop is invoked from within a method that needs to make further elaborations over the list of elements returned by the loop. This is a common situation, and the caller method needs to wait until the For..Each loop completes the iteration and populates the list of elements before it can use it. If the collection that For..Each is iterating is very large, this task can take a lot of time and the caller code will need to wait a while. With iterators, things change for the better—in fact, if you use iterators to loop through a collection, every item in the collection will be immediately sent to the caller via Yield so that the caller can start its elaboration, while the loop is still in progress and goes to completion. In this way an application is faster and more responsive. In the next paragraphs you get more detailed explanations about iterators via some code examples.


Iterator Functions’ Scope

Because they are methods, iterator functions can have the same scope of methods. So they can be Private, Public, Friend, Protected, or Protected Friend (except for when they are implemented in Module, where they can be Private, Friend, or Public).


Understanding the Benefits of Iterators in Code

Iterators have plenty of usages, and this chapter describes them all in detail. Before studying the many ways iterators can be useful, though, a better idea is understanding why they allow you to write better and more responsive code. Let’s imagine we have a collection of items of type Person and a method that iterates through the collection the way you know. Listing 16.1 demonstrates how to reproduce this simple scenario in code.

Listing 16.1. Iterating Through a Collection the Usual Way


Public Class Person
    Public Property FirstName As String
    Public Property LastName As String
    Public Property Age As Integer
End Class

Public Class People
    Inherits List(Of Person)


End Class

Module Module1
    Dim somePeople As New People

    Sub Main()
        'Populates the collection with a million items, for demo purposes
        For i As Integer = 1 To 1000000
            somePeople.Add(New Person With {.FirstName = "First Name: " & i.ToString,
                                            .LastName = "Last Name: " & i.ToString,
                                            .Age = i})
        Next

        For Each item As Person In GetPeople()
            Console.WriteLine("{0}, {1} of age: {2}", item.FirstName,
                              item.LastName, item.Age.ToString)
        Next
        Console.ReadLine()
    End Sub

    Function GetPeople() As IEnumerable(Of Person)
        Dim p As New People
        For Each item As Person In somePeople
            p.Add(item)
            Threading.Thread.Sleep(10)
        Next
        Return p
    End Function
End Module


Notice how a sample collection called People is created and populated with a million items for demo purposes. The GetPeople method iterates through the People collection and returns the result to the caller code (the Main method in this example). The code execution is delayed for 10 milliseconds (via Threading.Thread.Sleep) to force a small delay and provide a demonstration of performance improvement with iterators (in-memory collections are faster than other collections such as database tables, where you could instead face long running query operations). If you run this code, you will see that GetPeople needs to populate the local p collection before returning it as a result to the caller, so you will have to wait for this before being able to iterate the result and see the list of elements in the Console window. We can now rewrite the GetPeople method using iterators, as follows:

Iterator Function GetPeople() As IEnumerable(Of Person)
    For Each item As Person In somePeople
        Yield item
        Threading.Thread.Sleep(10)
    Next
End Function

This is an iterator function. It requires the Iterator modifier in its definition and returns an object of type IEnumerable(Of T), where T is now Person. If you run the edited code, you will see how every item of type Person is returned from GetPeople immediately and made available to the caller code for further elaborations, while the iteration continues its progress. Figure 16.3 shows a moment of the operation progress. In other words, you will not have to wait for all the iteration to complete before elaborating items that it stores. As a consequence, the whole process is faster and the application is more responsive.

Image

Figure 16.3. Iterators return items while the loop is still in progress.


Measuring Performances

You can add a System.Diagnostics.StopWatch object to the code and use its Start and Stop method to measure how much time the process takes and make comparisons between both implementations of GetPeople.


Now that you have understood the benefits of iterators, it is time to discuss them in more detail.

Simple Iterators

You can return elements from iterator functions with multiple Yield invocations. The following code demonstrates how to perform a loop over an iterator that returns a collection of strings:

Iterator Function StringSeries() As IEnumerable(Of String)
    Yield "First string"
    Yield "Second string"
    Yield "Third string"
    '...
End Function

Sub Main()
    For Each item As String In StringSeries()
        Console.WriteLine(item)
    Next
    Console.ReadLine()
End Sub

You can also use a single Yield statement inside a For..Each or For..Next loop. The following code demonstrates how to retrieve the list of odd numbers in a sequence of integers:

Iterator Function OddNumbers(first As Integer,
                  last As Integer) As IEnumerable(Of Integer)
    For number As Integer = first To last
        If number Mod 2 <> 0 Then
            'is odd
            Yield number
        End If
    Next
End Function

Sub Main()
    'Prints 1, 3, 5, 7, 9
    For Each number As Integer In OddNumbers(1, 10)
        Console.WriteLine(number)
    Next

    Console.ReadLine()
End Sub

In this case the caller code in the Sub Main will not need to wait for a collection of integers to be populated before the iteration occurs; instead, each number that comes from the Yield invocation is immediately printed to the Console window.

Exiting from Iterators

You can break the execution of an iterator by using either an Exit Function or a Return statement. The following snippet demonstrates how to exit from an iterator function:

Iterator Function StringSeries() As IEnumerable(Of String)
    Yield "First string"
    Yield "Second string"
    Exit Function 'Return is also accepted
    Yield "Third string"
    '...
End Function

Iterators with Try..Catch..Finally

Unlike Visual C#, in Visual Basic the Yield statement can be executed from within a Try statement of a Try..Catch..Finally block. The following code demonstrates how to rewrite the OddNumbers iterator function including error handling:

Iterator Function OddNumbers(first As Integer,
                  last As Integer) As IEnumerable(Of Integer)
    Try
        For number As Integer = first To last
            If number Mod 2 <> 0 Then
                'is odd
                Yield number
            End If
        Next
    Catch ex As Exception
        Console.WriteLine(ex.Message)
    Finally
        Console.WriteLine("Loop completed.")
    End Try
End Function

As you can see, the Yield statement is allowed inside the Try block.

Anonymous Iterators

Iterators can also be implemented as anonymous methods. These are discussed in more detail in Chapter 20, but for now all you need to know is that they are methods with no name and are defined on-the-fly inside a code block. The following code snippet demonstrates how to implement the previous iterator as an anonymous method:

'Type of oddSequence is inferred by the compiler
Dim oddSequence = Iterator Function(first As Integer,
                                    last As Integer) As IEnumerable(Of Integer)
                      Try
                          For number As Integer = first To last
                              If number Mod 2 <> 0 Then
                                  'is odd
                                  Yield number
                              End If
                          Next
                      Catch ex As Exception
                          Console.WriteLine(ex.Message)
                      Finally
                          Console.WriteLine("Loop completed.")
                      End Try
                  End Function

For Each number As Integer In oddSequence(1, 10)
    Console.WriteLine(number)
Next

As you can see, the iterator is defined as an in-line method without an explicit name and its result is assigned to a variable whose type is inferred by the compiler (in this case IEnumerable(Of Integer)).

Implementing an Iterator Class

Iterators can be useful in creating classes that act like lists. By implementing the IEnumerable interface and the GetEnumerator method of this interface, you can write efficient code that takes advantage of iterator functions to yield the content of a list. Listing 16.2 demonstrates how to create a class called BottlesOfWine that stores a list of bottles of wine given a name and a color of the wine (red or white). Internally it uses a List(Of BottleOfWine) instance, but instead of invoking the usual GetEnumerator method of the List class (provided by the IEnumerable interface that the List implements), it returns the list of bottles via an iterator function. It also returns a list of bottles based on their color.

Listing 16.2. Implementing a Collection Class That Uses Iterators


Public Class BottleOfWine
    Property Brand As String
    Property Color As WineColor
End Class

Public Enum WineColor
    Red
    White
End Enum

Public Class BottlesOfWine
    Implements IEnumerable(Of BottleOfWine)

    Private _bottles As New List(Of BottleOfWine)

    Public Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
        Return Me._bottles.GetEnumerator
    End Function

    Public Iterator Function GetEnumerator() As IEnumerator(Of BottleOfWine) Implements IEnumerable.GetEnumerator(Of BottleOfWine)

        'Use Yield instead of List.GetEnumerator
        For Each bottle As BottleOfWine In Me._bottles
            Yield bottle
        Next
    End Function

    Private Iterator Function BottlesByColor(ByVal color As WineColor) As IEnumerable _
            (Of BottleOfWine)
        For Each bottle As BottleOfWine In Me._bottles
            If bottle.Color = color Then
                Yield bottle
            End If
        Next
    End Function

    Public ReadOnly Property RedBottles As IEnumerable(Of BottleOfWine)
        Get
            Return BottlesByColor(WineColor.Red)
        End Get
    End Property

    Public ReadOnly Property WhiteBottles As IEnumerable(Of BottleOfWine)
        Get
            Return BottlesByColor(WineColor.White)
        End Get
    End Property

    Public Sub AddRedBottle(ByVal name As String)
        Me._bottles.Add(New BottleOfWine With {.Brand = name, .Color = WineColor.Red})
    End Sub

    Public Sub AddWhiteBottle(ByVal name As String)
        Me._bottles.Add(New BottleOfWine With {.Brand = name, .Color = WineColor.White})
    End Sub
End Class


By returning with Yield the result of both the GetEnumerator and of BottlesByColor methods, the code is more responsive. You can then use the code easily—for example, creating a list of bottles of red wine and then iterating the list in a way that is efficient because the result takes advantage of iterators, as in the following code:

 Dim bottles As New BottlesOfWine
 bottles.AddRedBottle("Chianti")
 bottles.AddRedBottle("Cabernet")

 'Result is returned via an iterator function
 For Each bottle As BottleOfWine In bottles
     Console.WriteLine(bottle.Brand)
Next


Iterators are an Opportunity, Not the Rule

Iterators provide an additional opportunity to write efficient code, but of course they are not necessary everywhere. You typically use iterators with large collections of objects and over long-running operations. So, they are not a new rule for iterating collections. For this reason, in this book you will find code examples for collections that are not based on iterators, which is instead the general rule. In particular circumstances, samples based on iterators are offered.


Summary

Applications often require data access. You store data within classes and structures, but often you need to group a set of data and collections to help you in this task. The .NET Framework offers both nongeneric collections (such as ArrayList, Queue, Stack, and HashTable) and generic ones (such as List(Of T), ObservableCollection(Of T), Dictionary(Of TKey, TValue), Queue(Of T), and Stack(Of T)). In both cases you can add, remove, and edit items within collections using the same members (due to the interfaces implementations). Moreover, with the feature of collection initializers, you can instantiate and populate collections inline. Although you typically use collections for manipulating data, the .NET Framework provides some special read-only collections, such as ReadonlyCollection(Of T). You learned to create custom collections, which is not an uncommon scenario, and finally you learned a new efficient way to iterate over collections via iterator functions and the Yield keyword.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.149.238