Section 14.4. Indexers and the Default Property

   

14.4 Indexers and the Default Property

Some classes contain their own internal collection. For example, you might write your own School class that would contain, as a private member variable, a collection of the Students enrolled in the school. You might then want to access the School class as if it were an array of Students. To do so, you would use the default property, which will allow you to write:

 Dim joe As Student = mySchool(5) 

accessing the sixth element in mySchool's internal collection!

As another example, suppose you create a listbox control named myListBox that contains a list of strings stored in a one-dimensional array, a private member variable named myStrings. A listbox control contains member properties and methods in addition to its array of strings. However, it would be convenient to be able to access the listbox array with an index, just as if the listbox were an array. For example, such a property would permit statements like the following:

 Dim theFirstString As String = myListBox(0) 

You implement this with the default property. Each class can have one default property, designated with the Default keyword. It is common to use the property name Item for the default property, but that is not required.

You can retrieve the default property with or without the property name. The following two code lines both retrieve the default property (which in this case, is called Item); the first uses the name, the second doesn't:

 Dim theFirstString As String = myListBox.Item(0) Dim theFirstString As String = myListBox(0) 

In either case, the default property is acting as an indexer , a property used to index into the class as if it were a collection.

Example 14-10 declares a listbox control class that contains a simple array (myStrings) and a default property (Item) that acts as an indexer for accessing its contents. To keep the example simple, you'll strip the listbox control down to the few features.

The listing ignores everything having to do with being a user control and focuses only on the list of strings the listbox maintains and methods for manipulating them. In a real application, of course, these are a small fraction of the total methods of a listbox, whose principal job is to display the strings and enable user choice.

Example 14-10. Indexer
 Option Strict On Imports System Namespace Indexers     ' a simplified ListBox control     Public Class ListBoxTest         Private strings(255) As String         Private ctr As Integer = 0         ' initialize the list box with strings         Public Sub New(ByVal ParamArray initialStrings( ) As String)             Dim s As String             ' copy the strings passed in to the constructor             For Each s In initialStrings                 strings(ctr) = s                 ctr += 1             Next         End Sub         ' add a single string to the end of the list box         Public Sub Add(ByVal theString As String)             If ctr >= Strings.Length Then                 ' handle bad index             Else                 Strings(ctr) = theString                 ctr += 1             End If         End Sub         ' allow array-like access         Default Public Property Item(ByVal index As Integer) As String             Get                 If index < 0 Or index >= strings.Length Then                     ' handle bad index                 Else                     Return strings(index)                 End If             End Get             Set(ByVal Value As String)                 If index >= ctr Then                     ' handle error                 Else                     strings(index) = Value                 End If             End Set         End Property         ' publish how many strings you hold         Public Function Count( ) As Integer             Return ctr         End Function     End Class     Public Class Tester         Public Sub Run( )             ' create a new list box and initialize             Dim lbt As New ListBoxTest("Hello", "World")             Dim i As Integer             Console.WriteLine("After creation...")             For i = 0 To lbt.Count - 1                 Console.WriteLine("lbt({0}): {1}", i, lbt(i))             Next             ' add a few strings             lbt.Add("Who")             lbt.Add("Is")             lbt.Add("John")             lbt.Add("Galt")             Console.WriteLine("After adding strings...")             For i = 0 To lbt.Count - 1                 Console.WriteLine("lbt({0}): {1}", i, lbt(i))             Next             ' test the access             Dim subst As String = "Universe"             lbt(1) = subst             ' access all the strings             Console.WriteLine("After editing strings...")             For i = 0 To lbt.Count - 1                 Console.WriteLine("lbt({0}): {1}", i, lbt(i))             Next         End Sub         Public Shared Sub Main( )             Dim t As New Tester( )             t.Run( )         End Sub     End Class End Namespace 
  Output:  After creation... lbt(0): Hello lbt(1): World After adding strings... lbt(0): Hello lbt(1): World lbt(2): Who lbt(3): Is lbt(4): John lbt(5): Galt After editing strings... lbt(0): Hello lbt(1): Universe lbt(2): Who lbt(3): Is lbt(4): John lbt(5): Galt 

Example 14-10 begins by creating two private member variables , strings and ctr:

 Private strings(255) As String Private ctr As Integer = 0 

In this program, the listbox maintains a simple array of strings, named (appropriately) strings. The member variable ctr keeps track of how many strings are added to the array.

The constructor initializes the array with the strings passed in as parameters. Because you cannot know how many strings will be added, you use the keyword ParamArray , as described earlier in this chapter.

 Public Sub New(ByVal ParamArray initialStrings( ) As String)     Dim s As String          ' copy the strings passed in to the constructor     For Each s In initialStrings         strings(ctr) = s         ctr += 1     Next End Sub 

Our focus is on the default property, Item, created using the following code:

 Default Public Property Item(ByVal index As Integer) As String 

In Example 14-10, the Get( ) method endeavors to implement rudimentary bounds checking, and assuming the index requested is acceptable, it returns the value requested :

 Get     If index < 0 Or index >= strings.Length Then         ' handle bad index     Else         Return strings(index)     End If End Get 

The Set( ) method checks to make sure that the index you are setting already has a value in the list box. If not, it treats the set as an error; note that new elements can only be added using Add with this approach. The Set( ) accessor takes advantage of the implicit parameter value that represents whatever is assigned to the property.

 Set(ByVal Value As String)     If index >= ctr Then         ' handle error     Else         strings(index) = Value     End If End Set 

Thus, if you write:

 lbt(5) = "Hello World" 

the compiler will call the default property Item's Set( ) method on your object and pass in the string "Hello World" as an implicit parameter-named value.

14.4.1 Default Properties and Assignment

In Example 14-10, you cannot assign to an index that does not have a value. Thus, if you write:

 lbt(10) = "wow!" 

you trigger the error handler in the Set( ) method, which would note that the index you've passed in (10) is larger than the counter (6).

Of course, you can use the Set( ) method for assignment; you simply have to handle the indexes you receive. To do so, you might change the Set( ) method to check the Length property of the buffer rather than the current value of the counter (ctr). If a value was entered for an index that did not yet have a value, you would update ctr:

 Set(ByVal Value As String)     If index >= strings.Length Then         ' handle error     Else         strings(index) = Value         if ctr < index + 1 then           ctr = index + 1         end if     End If End Set 

This allows you to create a "sparse" array in which you can assign to offset 10 without ever having assigned to offset 9. Thus, if you were to write:

 lbt(10) = "wow!" 

the output would be:

 lbt(0): Hello lbt(1): Universe lbt(2): Who lbt(3): Is lbt(4): John lbt(5): Galt lbt(6): lbt(7): lbt(8): lbt(9): lbt(10): wow! 

In the Run( ) method of Example 14-10, you create an instance of the ListBoxTest class named lbt and pass in two strings as parameters:

 Dim lbt As New ListBoxTest("Hello", "World") 

You then call Add( ) to add four more strings:

 lbt.Add("Who") lbt.Add("Is") lbt.Add("John") lbt.Add("Galt") 

Finally, you modify the second value (at index 1):

 Dim subst As String = "Universe" lbt(1) = subst 

At each step, you display each value in a loop:

 For i = 0 To lbt.Count - 1     Console.WriteLine("lbt({0}): {1}", i, lbt(i)) Next 

14.4.2 Indexing on Other Values

VB.NET does not require that you always use an integer value as the index to a collection. When you create a custom collection class and create your indexer, you are free to overload the default property so that a given collection can be indexed ”for example, by an integer value or by a string value, depending on the needs of the client.

In the case of your listbox, you might want to be able to index into the listbox based on a string. Example 14-11 illustrates a string index. Example 14-11 is identical to Example 14-10 except for the addition of an overloaded default property, which can match a string, and findString( ), a helper method created to support that index. The indexer calls findString( ) to return a record based on the value of the string provided.

Notice that the overloaded indexer of Example 14-11 and the indexer from Example 14-10 are able to coexist. The complete listing is shown, followed by the output and then a detailed analysis.

Example 14-11. String indexer
 Option Strict On Imports System Namespace Indexers     ' a simplified ListBox control     Public Class ListBoxTest         Private strings(255) As String         Private ctr As Integer = 0         ' initialize the list box with strings         Public Sub New(ByVal ParamArray initialStrings() As String)             Dim s As String             ' copy the strings passed in to the constructor             For Each s In initialStrings                 strings(ctr) = s                 ctr += 1             Next         End Sub         ' add a single string to the end of the list box         Public Sub Add(ByVal theString As String)             If ctr >= strings.Length Then                 ' handle bad index             Else                 strings(ctr) = theString                 ctr += 1             End If         End Sub         ' allow array-like access         Default Public Property Item( _            ByVal index As Integer) As String             Get                 If index < 0 Or index >= strings.Length Then                     ' handle bad index                 Else                     Return strings(index)                 End If             End Get             Set(ByVal Value As String)                 If index >= ctr Then                     ' handle error                 Else                     strings(index) = Value                 End If             End Set         End Property         ' index on string         Default Public Property Item( _            ByVal index As String) As String             Get                 If index.Length = 0 Then                     ' handle bad index                 Else                     Return strings(findString(index))                 End If             End Get             Set(ByVal Value As String)                 strings(findString(index)) = Value             End Set         End Property         ' helper method, given a string find         ' first matching record that starts with the target         Private Function findString( _            ByVal searchString As String) As Integer             Dim i As Integer             For i = 0 To strings.Length - 1                 If strings(i).StartsWith(searchString) Then                     Return i                 End If             Next             Return -1         End Function         ' publish how many strings you hold         Public Function Count() As Integer             Return ctr         End Function     End Class     Public Class Tester         Public Sub Run()             ' create a new list box and initialize             Dim lbt As New ListBoxTest("Hello", "World")             Dim i As Integer             Console.WriteLine("After creation...")             For i = 0 To lbt.Count - 1                 Console.WriteLine("lbt({0}): {1}", i, lbt(i))             Next             ' add a few strings             lbt.Add("Who")             lbt.Add("Is")             lbt.Add("John")             lbt.Add("Galt")             Console.WriteLine(vbCrLf & "After adding strings...")             For i = 0 To lbt.Count - 1                 Console.WriteLine("lbt({0}): {1}", i, lbt(i))             Next             ' test the access             Dim subst As String = "Universe"             lbt(1) = subst             lbt("Hel") = "GoodBye"             ' access all the strings             Console.WriteLine(vbCrLf & "After editing strings...")             For i = 0 To lbt.Count - 1                 Console.WriteLine("lbt({0}): {1}", i, lbt(i))             Next         End Sub         Public Shared Sub Main()             Dim t As New Tester()             t.Run()         End Sub     End Class End Namespace 
  Output:  lbt[0]: GoodBye lbt[1]: Universe lbt[2]: Who lbt[3]: Is lbt[4]: John lbt[5]: Galt 

In Example 14-11, the findString( ) method simply iterates through the strings held in myStrings until it finds a string that starts with the target string used in the index. If found, it returns the index of that string; otherwise it returns the value -1.

You can see in Main( ) that the user passes in a string segment to the index, just as was done with an integer:

 lbt("Hel") = "GoodBye" 

This calls the overloaded default property, which does some rudimentary error checking (in this case, making sure the string passed in has at least one letter) and then passes the value (Hel) to findString( ). It gets back an index and uses that index to index into the strings array:

 Return strings(findString(index)) 

The set accessor works in the same way:

 strings(findString(index)) = Value 

If the string does not match, a value of -1 is returned, which is then used as an index into myStrings. This action then generates an exception (System.NullReferenceException), as you can see by un-commenting the following line in Main( ):

 lbt["xyz"] = "oops" 

The proper handling of not finding a string is, as they say, left as an exercise for the reader. You might consider displaying an error message or otherwise allowing the user to recover from the error. Exceptions are discussed in Chapter 17.

   


Learning Visual Basic. NET
Learning Visual Basic .Net
ISBN: 0596003862
EAN: 2147483647
Year: 2002
Pages: 153
Authors: Jesse Liberty

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net