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 |
| Defualt Property SomePropertyName(ByVal someIndex as Integer) as WhateverType |
| Property SomePropertyName(ByVal someIndex as Integer) as WhateverType |
| 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 |
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 |
| myDataReader = DataHandler.GetMyData("SELECT * FROM myTable") Instead of: Dim dh as New DataHandler() myDataReader = DataHandler.GetMyDate("SELECT * FROM myTable") |
| 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 |