Non-Zero Lower Bounds
Once upon a time, Visual Basic allowed you to create arrays with non-zero lower bounds. That way, if you wanted to store decimal sales figures for the years 1991 through 2010, you could create an array with corresponding bounds like this:
Dim sales(1991 To 2010) As Decimal
Unfortunately in Visual Studio .NET (both C# and Visual Basic), all arrays use zero for their lower bounds. (As a largely symbolic concession, Microsoft now allows you to explicitly declare the lower bound in Visual Basic as long as it's zero. For example, you can write: Dim people(0 To 100)
—but you still can't have non-zero lower bounds.)
Although Visual Studio doesn't allow arrays with non-zero lower bounds, you can work around that restriction with a little simple subtraction.
For example, suppose you want to build an array of sales figures for the years 1991 through 2010. You can save the values in a zero-based array and subtract 1991 from the indexes you use to get and fetch values. For example, you would place the value for the year 2000 in array entry 2000—1991 = 9. The following code makes an array big enough to hold the values and then places the value 32,000 in the entry for 2000:
Dim sales(2010 - 1991) As Decimal
sales(2000 -- 1991) = 32000
Unfortunately the extra subtraction makes your code slightly harder to read. The following OneDArray class wraps up the details to protect those who are math phobic. Notice that the class is generic, so you can use it to make an array of strings, decimals, pickles, politicians, or whatever (error-checking code that verifies bounds and indexes omitted for brevity):
Public Class OneDArray(Of T)
Private m_LowerBound As Integer
Private m_UpperBound As Integer
Private m_Values() As T
Public Sub New(ByVal lower_bound As Integer, _
ByVal upper_bound As Integer)
' Save the bounds.
m_LowerBound = lower_bound
m_UpperBound = upper_bound
' Create the storage array.
ReDim m_Values(m_UpperBound - m_LowerBound)
Default Public Property Item(ByVal index As Integer) As T
Return m_Values(index - m_LowerBound)
Set(ByVal Value As T)
m_Values(index - m_LowerBound) = Value
The constructor saves the desired upper and lower bounds and makes a normal array to hold the values. The Item
property subtracts the lower bound from the index to find the desired entry in the value array.
keyword added to the property definition makes this the default property (the class indexer in C#) for the class. Again, that means a program can treat an object of the class as if it were an array and the "index" passed to the object is sent as a parameter to the property.
The following code shows the previous example rewritten to use the OneDArray class. Notice how the second line treats the sales object as if it were an array:
Dim sales As New OneDArray(Of Integer)(1991, 2010)
sales(2000) = 32000
This idea is easy to generalize for higher-dimensional arrays. You simply save the bounds for all dimensions and subtract them from the indexes used to get and set values in the array. The following code shows the key parts of the TwoDArray class, which implements a two-dimensional array with non-zero lower bounds:
Public Class TwoDArray(Of T)
Private m_LowerBound0 As Integer
Private m_LowerBound1 As Integer
Private m_UpperBound0 As Integer
Private m_UpperBound1 As Integer
Private m_Values(,) As T
Public Sub New( _
ByVal lower_bound0 As Integer, _
ByVal upper_bound0 As Integer, _
ByVal lower_bound1 As Integer, _
ByVal upper_bound1 As Integer)
' Save the bounds.
m_LowerBound0 = lower_bound0
m_LowerBound1 = lower_bound1
m_UpperBound0 = upper_bound0
m_UpperBound1 = upper_bound1
' Create the two-dimensional storage array.
ReDim m_Values( _
m_UpperBound0 - m_LowerBound0, _
m_UpperBound1 - m_LowerBound1)
Default Public Property Item( _
ByVal index0 As Integer, ByVal index1 As Integer) As T
Return m_Values(index0 - m_LowerBound0, _
index1 - m_LowerBound1)
Set(ByVal Value As T)
m_Values(index0 - m_LowerBound0, _
index1 - m_LowerBound1) = Value
As a usage example, suppose you want to save sales figures for each of the four quarters in the years 1991 through 2010. The following code creates a TwoDArray object to represent the year in its first dimension and the fiscal quarter in the second. It then sets the third quarter sales figure for the year 2000 to 8,000:
Dim sales As New TwoDArray(Of Decimal)(1991, 2010, 1, 4)
sales(2000, 3) = 8000
You can extend this idea in a similar manner to accommodate three, four, or more dimensions. Unfortunately, subtraction alone isn't enough to make a single general class that can handle any number of dimensions the way the Array class can. To do that, you need to use some higher mathematics: multiplication and addition.