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 9-10 declares a listbox control class that contains a simple array (strings) 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 a 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.
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 listbox 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 listbox
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 listbox 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 9-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 9-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 listbox. If not, it treats the set as an error; note that new elements can only be added using the Add( ) method with this approach. The Set( ) accessor takes advantage of the implicit parameter value, which 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.
In Example 9-10, you cannot assign to an index that does not have a value. Thus, if you write:
lbt(10) = "wow!"
you would 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 9-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
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 9-11 illustrates a string index. Example 9-11 is identical to Example 9-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 9-11 and the indexer from Example 9-10 are able to coexist. The complete listing is shown, followed by the output, and then a detailed analysis.
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 listbox 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 listbox 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 listbox 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 9-11, the findString( ) method simply iterates through the strings held in strings 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:
Set(ByVal Value As String) strings(findString(index)) = Value End Set
If the string does not match, a value of -1 is returned, which is then used as an index into strings. This action then generates an exception (System.NullReferenceException), as you can see by un-commenting the following line in Main( ):
lbt["xyz"] = "oops"
|
Top |