< Day Day Up > |
It is sometimes desirable to expose information as if it were a field on a type, even though the information may be calculated from multiple variables rather than stored in a single variable. For example, an Order class that contains Quantity and Cost fields may want to expose the total amount of the order. This can be done as a function, but it would be ideal if the information could be exposed as if it were a field. Class Order Public Cost As Double Public Quantity As Integer Public Function Total() As Double Return Cost * Quantity End Function End Class Module Test Sub Main() Dim Order As Order = New Order() Order.Cost = 34.32 Order.Quantity = 5 Console.WriteLine(Order.Total()) End Sub End Module Properties are type members that behave as if they were fields but that work like methods . In the preceding example, instead of exposing the order total as a function, we can use a property. Class Order Public Cost As Double Public Quantity As Integer Public ReadOnly Property Total() As Double Get Return Cost * Quantity End Get End Property End Class Module Test Sub Main() Dim Order As Order = New Order() Order.Cost = 34.32 Order.Quantity = 5 Console.WriteLine(Order.Total) End Sub End Module Like a field, every property has a type that defines what kind of information it accepts or returns. The property also defines methods called accessors that are used to access the value of the property. The Get accessor of a property reads the value of the property, while the Set accessor writes the value of the property. The accessor methods can contain any code necessary to accomplish the particular operation. For example, a property can be used to validate the value being assigned to a field. Class Order Private _Cost As Double Public Property Cost() As Double Get Return _Cost End Get Set (ByVal Value As Double) If Value <= 0 Then Throw New ArgumentException("Cost cannot be zero or less.") End If _Cost = Value End Set End Property Public Quantity As Integer End Class In this example, the _Cost field is kept private and only exposed through a public property. The property Get accessor just returns the field's value, but the property's Set accessor validates that the property is not being assigned a value of zero or less.
A Set accessor is followed by a parameter that represents the value being assigned to the property. Only one parameter is allowed in the parameter list, and the type must match the type of the property itself. If a property omits a Set accessor, the property is read-only and cannot be assigned to. Read-only properties must additionally be declared with the ReadOnly modifier. If a property omits a Get accessor, the property is write-only and cannot be assigned to. Write-only properties must additionally be declared with the WriteOnly modifier. For example: Class Order Private _Cost As Double Public WriteOnly Property Cost() As Double Set (ByVal Value As Double) If Value <= 0 Then Throw New ArgumentException("Cost cannot be zero or less.") End If _Cost = Value End Set End Property End Class Module Test Sub Main() Dim Order As Order = New Order() ' OK, property has a setter Order.Cost = 10 ' Error, property is write-only Console.WriteLine(Order.Cost) End Sub End Module
To illustrate the advanced point, consider this example. Structure Order Public Cost As Double Public Quantity As Integer End Structure Class Customer Private _Order As Order Public Property Order() As Order Get Return _Order End Get Set (Value As Order) _Order = Value End Set End Property End Class Module Test Sub ChangeOrderCost(ByVal c As Customer) ' Error: The value of Cost cannot be changed directly c.Order.Cost = 10.34 ' OK: The value of Cost is not changed directly Dim o As Order = c.Order o.Cost = 10.34 c.Order = o End Sub End Module In this example, the expression c.Order returns the actual Order itself because Order is a structure. If c.Order.Cost = 10.34 were allowed to work, it would only change the Cost field of the Order value that was returned from the property, not the actual _Order field of the Customer class. The second part of the ChangeOrderCost subroutine shows how to change the Cost field of the Customer 's Order field. Copying the value back into the Order property updates the _Order field of the Customer class. Indexed PropertiesProperties can also take parameters; such properties are called indexed properties because when they are used, they are indexed in the same manner that arrays are. For example, the following code defines an OrderCollection object that functions the same as an array of Order objects. Class Order Public Cost As Double Public Quantity As Integer End Class Class OrderCollection Private _Orders(10) As Order Public Property Orders(ByVal Index As Integer) As Order Get If _Orders(Index) Is Nothing Then _Orders(Index) = New Order() End If Return _Orders(Index) End Get Set (Value As Order) _Orders(Index) = Value End Set End Property End Class Module Test Sub Main() Dim OrderCollection As New OrderCollection() OrderCollection.Orders(5).Cost = 10.34 Console.WriteLine(OrderCollection.Orders(5).Cost) End Sub End Module The parameter list of a property works exactly the same as the parameter list of a function, with the exception that property parameters cannot be declared ByRef .
Properties can be overloaded based on their parameter lists, though not on their type. This is the same as for methods, which can only be overloaded on their parameter list and not their return type. For example: Class Customer Public Name As String End Class Class CustomerCollection Private _Customers(10) As Customer Public ReadOnly Property Customer(ByVal Index As Integer) As _Customer Get If _Customers(Index) Is Nothing Then _Customers(Index) = New Customer() End If Return _Customers(Index) End Get End Property Public ReadOnly Property Customer(ByVal Name As String) As Customer Get For Each CurrentCustomer As Customer In _Customers If CurrentCustomer.Name = Name Then Return CurrentCustomer End If Next CurrentCustomer Throw New ArgumentException("No customer by that name.") End Get End Property End Class Module Test Sub Main() Dim Customers As New CustomerCollection() Console.WriteLine(Customers.Customer(5).Name) Console.WriteLine(Customers.Customer("Harry").Name) End Sub End Module In the example, the Customers indexed property is overloaded on both Integer and String . This allows indexing the property by using either a numerical index or a customer name. Like methods, indexed properties can be called late bound if the type of the variable being indexed is Object . For example, the following code defers checking for indexed properties until runtime. Module Test Sub Main() Dim o As Object = New OrderCollection() o.Orders(5).Cost = 10.34 Console.WriteLine(o.Orders(5).Cost) End Sub End Module
Default PropertiesAn indexed property can be declared as the default property of a type. This allows the type to be indexed as if it were the default property. In the following example, when the index is applied to the Customers variable, it is equivalent to applying the index to the default property of the CustomerCollection class. Class Customer Public Name As String End Class Class CustomerCollection Private _Customers(10) As Customer Public Default Property Customer(ByVal Index As Integer) As Customer Get If _Customers(Index) Is Nothing Then _Customers(Index) = New Customer() End If Return _Customers(Index) End Get Set (Value As Customer) _Customers(Index) = Value End Set End Property End Class Module Test Sub Main() Dim Customers As New CustomerCollection() ' Customers(5).Name is equivalent to Customers.Customer(5).Name Customers(5).Name = "John Doe" Console.WriteLine(Customers(5).Name) End Sub End Module In this example, the index expression 5 is applied directly to the Customers variable. The compiler knows to interpret Customers(5) as Customers.Customer(5) instead. Default properties must have index parameters.
Dictionary LookupA class with a default property that is indexed by a single String parameter can be accessed in a shorthand way by using the dictionary lookup operator . This operator (also called the bang operator because it uses an exclamation point) takes the identifier that follows it and uses it as the parameter to the default indexed property. For example: Class Customer Public Name As String End Class Class CustomerCollection Private _Customers(10) As Customer Public ReadOnly Default Property Customer(ByVal Name As String) _ As Customer Get For Each CurrentCustomer As Customer In _Customers If CurrentCustomer.Name = Name Then Return CurrentCustomer End If Next CurrentCustomer Throw New ArgumentException("No customer by that name.") End Get End Property End Class Module Test Sub Main() Dim Customers As New CustomerCollection() ' Using a regular indexed property Console.WriteLine(Customers("Harry").Name) ' Using the dictionary lookup operator Console.WriteLine(Customers!Harry.Name) End Sub End Module In this example, the second WriteLine statement is equivalent to the first ”the string Harry after the exclamation point is passed to the default property as a string. The dictionary lookup operation can also be used in a With statement. Module Test Sub Main() Dim Customers As New CustomerCollection() With Customers Console.WriteLine(!Harry.Name) Console.WriteLine(!John.Name) Console.WriteLine(!Tom.Name) End With End Sub End Module |
< Day Day Up > |