|
|
Building Better Properties...a case for using them | |
So why use Properties over Public Variables?
The first reason is really simple - you don't break encapsulation. This sounds rather trite but if you think about it for a minute, it's really important. If you write object libraries, then it's absolutely critical. Why? Well, let's use a common example of a control that we are all familiar with, the Windows.Forms.TextBox control. The most common property that you use with it is probably the .text property. The .enabled and .visible properties are two other common ones. Well, it's pretty common to use the TextChanged event to validate your input and often times enable other controls based on its validity (there are other ways to accomplish this, but I'm just trying to use a common example). Now, if .text were declared as a public property, how could you trap the textchanged event? Well, you could trap KeyDown or some other event but those are all based on properties as well. So if you hade only public variables instead of properties, guess what? You wouldn't have events. That would make programming a little less fun wouldn't you agree? So what happens behind the scenes? Why is this so? Well, it's pretty simple. In any given class, you can define any event you want. Even if you had public variables, you could still have events, like TextChanged. The problem stems from the fact that without properties, how would you raise this event from inside your class? Since encapsulation is broken, outside entities can change the value without the permission of a gate keeper (the property). So how could you know it changed from inside the class? Trust me on this, any solution, and I've heard some pretty intersting ones, is way more trouble than it's worth, and even if it works, will be very coupled with the variable. However, in the textbox example, every time a call to the Set method of the property is made, you can raise an event, TextChanged, inside your class. Then, whereever the textbox is declared, or your object, you can write an event handler and viola' you have events and handlers.
The next reason is validation of data. If you use a public variable instead of a property, how can you validate it? The common response you'll hear is that you can validate it in the caller. This only has any validity if you are the only one that uses your class(es) but that means you are going to have to write a lot of unnecessary validation code and no one can use your class without knowing the inner workings of it. That kind of goes against the entire grain of OOP don't you think? Even if you don't mind writing the extra code, you'd have to agree that you wouldn't want to use someone else's classes that you had to guess about appropriate values and guard against invalid values every time you call it? Still not convinced? Well, if a ListBox's Item property was implemented as an Integer and as a public variable, you could set item -32,600 and you'd never know that was a problem until someone tried it and your control blew up. And who would do that? Idiots? Well, them for sure. So would newbies, people who made a typo, and advanced programmers trying to learn more about your class. And what are they going to find out? That your code is buggy. Think of how many 'bugs' and shortcomings people find in VB.NET or C# just by people trying to do things in ways that weren't intended. And trust me, when those are discovered no one ever says anything nice about it.
The next problem comes when you'd need to use indexed properties. Take a ComboBox for example. It has a Items collection right? So how would you implement this same functionality with a Public Variable? The typical answer you might here from a public variable advocate is something like "I'd declare it as an Arraylist or Array"...and even more likely one is "Why would you want to do that?" So let's go with the first one. We'll add an Arraylist and make it public. What happens when a programmer tries to add a DataReader to your collection when it was expecting a string? So you go away from this method and use an Array. After all, they are clearly typed right? Ok, so how do you overload that? Well, let me answer that for you....know matter how much code you write, you are't going to overload a variable. And one last thing on this subject, how do you make a public variable the default property or default indexed property of your class.
If nothing else, I hope I've got you to think about properties in a different way.
Default Properties?
One of my favorite examples of a well implemented default property is the DataReader (take your pick of favorite flavor) Item property (not the Item variable ;-) ) If I have a System.Data.SqlClient.SqlDataReader named 'dr', and I'm in the middle or a .Read loop, I can reference the first Item one of two ways.....
While dr.Read
Debug.WriteLine(dr(0)) 'OR
Debug.WriteLine(dr.Item(0))
End While |
Other the fact that I didn't strongly type my reference, this is pretty cool isn't it? Since Item is the main property you reference with DataReaders, giving it a default value allows a developer to skip the .Item reference. In order to declare a property as Defualt, you have to do something really difficult. You need to add the word Default before the word Property in your class. And no, it doesn't work with public variables.
So, to make this work, you'd do this:
| Defualt Property SomePropertyName(ByVal someIndex as Integer) as WhateverType |
Instead of this:
| Property SomePropertyName(ByVal someIndex as Integer) as WhateverType |
There's are some important caveats regarding default properties. You can only define Indexed Properties as Default. In VB6 this wasn't the case. Think about the label and textbox controls. In VB6 you could write myControl = "My Control" but in the .NET languages, the .Caption property isn't indexed, therefore not default. In addition, you can't declare Shared or Private Properties as Default. Finally, you can only have one default property per class. This makes sense after all, how else would the developer or compiler know what you wanted to do?
So how do you create these guys?
Class Programmer
Private _Languages as HashTable
Public Sub New()
'By Instantiating it here, we only have to reserve memory for members that we intend to use
_Languages = New HashTable
End Sub
Public Default Property Languages(ByVal Item as Integer) as String
Get
Return Ctype(_Languages(Item), String)
End Get
Set(ByVal Value as String)
_Languages(Item) = Value
End Set
End Property
End Class |
Shared Members are really cool!
Ok, if you came from a C derivative or Java, you are used to public static void main...the main word(no pun intended) being static. In C# static members are the equivalent to VB.NET shared members.
There's one very good reason to use these - you don't need to instantiate an object in order to call the method or get a value. For utility classes, ones that are very generic and suited to common tasks, you definitely want to take advantage of these. Think about this for instance: MessageBox is a class and Show is a shared/static member. How many times do you call MessageBox.Show in a typical program? Can you imagine if you had to instantiate a MessageBox object each time you wanted to use it, and then dispose of it? Would that be efficient in any way shape or form? What about the Math classes? Think about how many utility classes you probably have in your own library that don't need to be instantiated?
The only real catch is this. A shared member can't reference a instance variable. Don't forget this. However, if you want to still preserve encapsulation while using Shared properties, simply declare the private member as Both PRIVATE AND STATIC.
Class DataHandler
Private Shared cs as String = "Your connection string"
Private Shared cn as System.Data.SqlClient.SqlConnection
Private Shared cmd as System.Data.SqlClient.SqlCommand
Public Sub New()
cn = New SqlConnection
cmd = new SqlCommand
End Sub
Public Shared Function GetMyData(ByVal sql as String) as SqlDataReader
'Blah blah blah
Return myCommand.ExecuteReader
End Function
End Class
|
Then you can simply call this by using:
myDataReader = DataHandler.GetMyData("SELECT * FROM myTable")
Instead of:
Dim dh as New DataHandler()
myDataReader = DataHandler.GetMyDate("SELECT * FROM myTable")
|
Real Classes Raise Events!
Well, that's not necessarily true but I needed a good lead in. You may have remembered my little tirade about how lame public properties are. So I guess if I'm going to run my big mouth like that, I should probably show an example to support my argument.
Here goes...
Imports System
Imports System.Collections
'Add the rest up here |
Public Delegate Sub ConvenienceStoreEventHandler(ByVal Source as _
CoolConvenienceStore, ByVal _
e as CoolConvenienceStoreEventArgs)
Public Class CoolConvenienceStore
Public Event BoughtADietCoke as ConvenienceStoreEventHandler
Public Event BoughtLotteryTicket as ConvenienceStoreEventHandler
Private _DietCoke as String
Private _LotteryTicket as String
Public Sub New(ByVal i as ActionEnum)
Select Case i
Case ActionEnum.BuyDietCoke
_DietCoke = "Thank You, Come Again!"
Case ActionEnum.BuyLotteryTicket
_LotteryTicket = "You can't lose if you don't play"
End Select
End Sub
Public Sub Purchase(ByVal i as ActionEnum)
Select Case i
Case ActionEnum.BuyDietCoke
RaiseEvent BoughtADietCoke(me, myEventArgs)
Case ActionEnum.BuyLotteryTicket
RaiseEvent BoughtLotteryTicket(me, myEventArgs)
Case Else
'Do nothing, we only want to validate these two events
End Select
End Sub
End Class |
Now this class is a little silly, but that's the way I intended it. The constructor is where we actually initialize the private members and in reality, a non-trivial class will have multiple overloaded initializers in most instances. Moreover,the ActionEnum doesn't need to be passed in two places, like it was, but I was trying to emphasize a point. Finally, I used the ActionEnum to raise the event. Normally though, this is how we'd do our validation. Instead of buying a DietCoke, we'd make sure there was a valid member passed in and yell if not. Anyway, hopefully this will get you thinking a little more about properties.
Oh Yes, one Last thing I just came across. If you are using the security classes in your code...you can't protect public memebers, only properties and methods. I just came across this and will add more in one of my next articles. |
|