KnowDotNet Visual Organizer

Building Better Properties - Part II

A deeper look into building properties in .NET

by William Ryan
Print this Article Discuss in Forums

In my Article titled "Building Better Properties" I made a case for 'why' using properties is the 'right' way to build objects as opposed to using public variables.  If you come from VB6, it's very easy to mistakenly believe that public variables are a time saving shortcut to providing access to members of your class.  When I first came to .NET, I preferred properties due to my background in C++ but if someone would have truly grilled me on why properties were better, I wouldn't have been able to make a compelling argument other than "That's what my professors told me to do in college so I wouldn't break encapsulation".  Today I'm embarrassed I was so unopinionated about the subject.

Calculated Fields

Let's say that I had a class that represented a BillingItem.  I'd have another object called Bill that was a Collection of BillingItems.  A BillingItem had three basic fields, Quantity, Price and Total. [In practice it would probably have a BillID, Description etc, but we'll get to that in a minute].  So, if I used Public Variables, my Class might look something like this:

Public Class BillingItem
    
Public Quantity As Integer
    Public Price As Double
    Public Total As Double
End
Class


No, let's say that I wanted to use this class for a given order.  To use my public variables, my code would look something like this:

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
  
Dim Item1 As New BillingItem
   Item1.Price = "100.25"
   Item1.Quantity = "2"
   Item1.Total = Item1.Price * Item1.Quantity
   MessageBox.Show(Item1.Total.ToString)
End Sub

When I run the app, a MessageBox will appear and display "200.5" which is what we expect.  So, one could argue that this approach 'works'.  And it does.  However, who has the responsibility of ensuring that the values are valid?  Who has the responsibility of making sure Total is calculated?  Obviously the consumer of the class does. Now, the primary argument for public variables is that they save code, after all, you can accomplish in three lines what would otherwise take about 15.  But I'd like to point out that that argument is completely false.  While it's true that it saves the writer of the class some code, there's no such thing as a free lunch. And the developer using the class is going to have to take care of validation and ensuring that total is calculated each time.  At a minimum, if you don't include validation, this methodology will cost the consumer of this class one line of code each time he uses it b/c he'll have to compute Total.  Moreover, he'll have to compute Total each time the Price or Quantity change if he's already computed total because it's not dynamic.  Now, compare that with this implementation:

Public Class BillingItem
    
Public Quantity As Integer
    Public Price As Double
    Public ReadOnly Property Total() As Double
        Get
            Return Quantity * Price
        
End Get
    End Property
End
Class

This difference alone shifted the burden of calculating total off of the consumer, and ensures that Total is going to be correct whenever you check it provided Price and Quantity are valid.  Moreover, it centralizes the calculation, so a tired developer couldn't accidentally change the Total computation to Item1.Price + Item1.Quantity and not realize it until an angry customer called about it.

Ok, but now what about situation where Quantity or Price were invalid?  The consumer will still have to check this each time before calling total right?  Not if you use your properties correctly, one line of code can handle this at the class level, so the developer doesn't have to worry about it (I'm also going to improve upon this shortly):

Public ReadOnly Property Total() As Double
   Get
     If Quantity <= 0 Or Price <= 0 Then
      Throw New ArgumentException("Both Price and Quantity" & _
       " Must be greater than 0")
    
End If
     Return Quantity * Price
  
End Get
End
Property


Validation

This isn't the optimum solution, but it is a lot better than forcing the user to check the values each time so that erroneous calculations aren't entered.  But this solution stinks too because it doesn't prevent the user from entering a "0" or negative value, it just catches it if Total is called.  A better solution would be to validate the values at the property level, which you can't do with public variables.  Our new class would look something like this:

Public Class BillingItem

Private _Quantity As Integer
Private
_Price As Double

Public
Property Quantity() As Integer
        Get
            Return _Quantity
        
End Get
        Set(ByVal Value As Integer)
            
If Value <= 0 Then
                Throw New ArgumentException("Quantity Must be greater than 0")
            
Else
                _Quantity = Value
            
End If
        End Set
End
Property

Public
Property Price() As Double
        Get
            Return _Price
        
End Get
        Set(ByVal Value As Double)
            
If Value <= 0 Then
                Throw New ArgumentException("Price must be greater than 0")
            
Else
                _Price = Value
            
End If
        End Set
End
Property

Public
ReadOnly Property Total() As Double
        Get
            'We'll leave this check in as well in case they don't set the
            'values
            If Quantity <= 0 Or Price <= 0 Then
                Throw New ArgumentException("Both Price and Quantity" & _
                 " Must be greater than 0")
            
End If
            Return Quantity * Price
        
End Get

    End Property
End
Class

Now, the user of our class doesn't have to worry about doing the validation on his end. He may want to to avoid an argument exception, but we know if he puts junk values in there, it won't computer which it would before.  And logic errors are the worst types.  Assume that we had another requirement that we need to raise a notification every time the Total changed.  How would you do it with public variables?  Well, the user of your class would need to remember to make a notification every time he called anything that changed the total.  By now, you should see a theme here, namely that if you use public variables, you as the user of your class, or some poor other sap is going to be doing a LOT of validation code in most instances.  And in all likelihood someone is going to forget something at some point which will hopefully result in an exception or if you're really unlucky, an elusive bug that causes some real damage.  To implement our Notification scheme, I'm going to create a Delegate called TotalChangedEventHandler, create an event call TotalChanged and add a handler in my client code.  Now, every time the TotalChange is referenced, I'll raise an event which can be trapped to notify the user.


Raising Events

I'll add these two declarations to the top of my class:

Public Event TotalChanged As TotalChangedEventHandler
Public Delegate Sub TotalChangedEventHandler()


Add this line to the Total Property:

RaiseEvent TotalChanged()
Return Quantity * Price

Add this to my form's code where I declared Item1:

AddHandler Item1.TotalChanged, AddressOf NotifyUser

Private Sub NotifyUser()
   MessageBox.Show("Total Changed")
End Sub


Wow, a whopping 8 lines of code that will raise a notification any time the object's Total changes.  Compare that to using public variables.  If you had under 8 instances of the BillItem class, than you'd save some code, but anything above that and you'd be costing code.  And if your class will only be used 8 times you probably need to rethink your object design strategy and make some more reusable objects.

Indexed Properties

Now, let's make this class a little cooler in a way that you couldn't do safely with public variables.  Let's make an indexed property (one of my personal favorites). Let's say that for any given BillType instances, there could be up to 5 reference items (reference item in the business sense, wherein someone changed the bill and had to sign there name to the change). To do this, we'll first create a ReferenceItems Class:

Public Class ReferenceItem
    
Private _ContactName As String
    Private _ContactPhone As String
    Private _ReferenceDate As DateTime

    
Public Property ContactName() As String
        Get
            Return _ContactName
        
End Get
        Set(ByVal Value As String)
            _ContactName = Value
        
End Set
    End Property
    Public Property ContactPhone() As String
        Get
            Return _ContactPhone
        
End Get
        Set(ByVal Value As String)
            _ContactPhone = Value
        
End Set
    End Property

    Private ReadOnly Property ReferenceDate() As DateTime
        
Get
            Return DateTime.Now
        
End Get
    End Property
End
Class

We used the Public modifier so it could be seen within our class and other classes that may want to use it.  Next, we create an array of ReferenceItems of 5 values:

Private _ReferenceItems(4) As ReferenceItem


Then create our property.  Since we now know how to use properties to validate our properties, we'll only allow valid indices to be passed in:

Public Property BillingReference(ByVal Index As Integer) As ReferenceItem
        
Get
            If _ReferenceItems(Index) Is Nothing Then
                Throw New IndexOutOfRangeException("Index Specified was not valid")
            
End If
            Return _ReferenceItems(Index)
        
End Get
        Set(ByVal Value As ReferenceItem)
            _ReferenceItems(Index) = Value
        
End Set
End
Property


Now, if we wanted to be cool, we could use the Default modifier in front of our indexed property and then people could reference our property like this:

Default Public Property BillingReference(ByVal Index As Integer) As ReferenceItem

Item1(0).ContactName = "Bill Ryan"


Value Types

Let's discuss one more topic before we conclude this part of the discussion. Lets discuss the use of Value and Reference types as properties, and Structs in particular.  Let's say that we made ReferenceItem a Struct and didn't have the validation rules.  We couldn't set the values in it:

Public Structure ReferenceItemStruct
    
Public ContactName As String
    Public ContactPhone As String
    Public ReferenceDate As DateTime
End Structure

Private
_ReferenceItemStructs As ReferenceItemStruct

Public Property ReferenceItemsStructure() As ReferenceItemStruct
    
Get
         Return _ReferenceItemStructs
    
End Get
     Set(ByVal Value As ReferenceItemStruct)
         _ReferenceItemStructs = Value
    
End Set
End
Property

Then, from our class, let's see how things behave differently:

'Won't Compile "Expression is a Value and Therefore can not be the
'Target of an assignment
Item1.ReferenceItemsStructure.ContactName = "Bill Ryan"
Dim NewStruct As ReferenceItemStruct
'Will Work
NewStruct.ContactName = Item1.ReferenceItemsStructure.ContactName


Why is this?  When a property is of type Structure, the fields can't be modified directly.  You can modify them from within the class, but that's it.  As Paul Vick  so elegantly points out in his Visual Basic .NET Programming Language
"This is because the property returns the value of the property directly, so changing the fileds of that value would not affect the value stored in that property."

Anyway, I think I've made a pretty good case on using properties vs. public variables and how to take advantage of them.  We've learned that you can raise events with properties, validate values within the class, calculate values without user intervention and use indexed properties.  And if you look at the amount of code needed to implement properties compared to what you save users of your class, I think the superstition of code efficiency with public variables is unquestionably wrong.

Writing Add-Ins for Visual Studio .NET
Writing Add-ins for Visual Studio .NET
by Les Smith
Apress Publishing