Lesson 10 - Properties in VB.NET
In the previous exercise, Solved tasks for OOP in VB .NET lesson 9, we've practiced our knowledge from previous lessons.
In the previous lesson, Solved tasks for OOP in VB .NET lesson 9, we learned about Shared (static) class members in VB.NET. In today's tutorial, we're going to look at the other class members that we haven't gone over yet.
Properties
We often want to have control over how an object field is being changed from outside of the class. We'd like to set the field as read-only or react to its changes somehow. Let's create a new project (name it Properties) and add the following Student class which will represent a student in a database.
Public Class Student Public fullName As String Public male As Boolean Public age As Integer Public fullAged As Boolean Public Sub New(fullName As String, gender As Boolean, age As Integer) Me.fullName = fullName Me.male = gender Me.age = age fullAged = True If age < 18 Then fullAged = False End If End Sub Public Overrides Function ToString() As String Dim iAmFullAged As String = "I'm" If Not fullAged Then iAmFullAged = "I'm not" End If Dim gender As String = "male" If Not male Then gender = "female" End If Return String.Format("I'm {0}, {1}. I'm {2} years old and {3} of age.", fullName, gender, age, iAmFullAged) End Function End Class
The class is very simple, the student has a name, gender, and age. The
fullAged field is set according to the age and provides a more
comfortable determination whether the student is of age from different places in
the system. We use a Boolean
value to store the gender, True
indicates that he's male. The constructor will determine whether the student is
of age or not. The ToString() method has been altered to suit our needs. In a
real world situation, it'd probably just return the student's name. Let's create
a student using the constructor:
{VBNET_OOP}
Public Class Student
Public fullName As String
Public male As Boolean
Public age As Integer
Public fullAged As Boolean
Public Sub New(fullName As String, gender As Boolean, age As Integer)
Me.fullName = fullName
Me.male = gender
Me.age = age
fullAged = True
If age < 18 Then
fullAged = False
End If
End Sub
Public Overrides Function ToString() As String
Dim iAmFullAged As String = "I'm"
If Not fullAged Then
iAmFullAged = "I'm not"
End If
Dim gender As String = "male"
If Not male Then
gender = "female"
End If
Return String.Format("I'm {0}, {1}. I'm {2} years old and {3} of age.", fullName, gender, age, iAmFullAged)
End Function
End Class
{VBNET_MAIN_BLOCK}
Dim s As Student = New Student("Peter Brown", True, 20)
Console.WriteLine(s)
{/VBNET_MAIN_BLOCK}
{/VBNET_OOP}
The output:
Console application
I'm Peter Brown, male. I'm 20 years old and I am of age.
Everything looks nice, but the fields can be re-written. We can break the object like this, so it'll no longer function properly (i.e. would have an inconsistent internal state):
{VBNET_OOP}
Public Class Student
Public fullName As String
Public male As Boolean
Public age As Integer
Public fullAged As Boolean
Public Sub New(fullName As String, gender As Boolean, age As Integer)
Me.fullName = fullName
Me.male = gender
Me.age = age
fullAged = True
If age < 18 Then
fullAged = False
End If
End Sub
Public Overrides Function ToString() As String
Dim iAmFullAged As String = "I'm"
If Not fullAged Then
iAmFullAged = "I'm not"
End If
Dim gender As String = "male"
If Not male Then
gender = "female"
End If
Return String.Format("I'm {0}, {1}. I'm {2} years old and {3} of age.", fullName, gender, age, iAmFullAged)
End Function
End Class
{VBNET_MAIN_BLOCK}
Dim s As Student = New Student("Peter Brown", True, 20)
s.age = 15
s.male = False
Console.WriteLine(s)
Console.ReadKey()
{/VBNET_MAIN_BLOCK}
{/VBNET_OOP}
The output:
Console application
I am Peter Brown, female. I am 15 years old and I am of age.
Certainly, we would want fullAged to be updated when the age is
changed. Aside from that, no other field would need to be altered externally.
Students don't usually change their genders or names. However, want to keep the
properties accessible for reading, so we can't make them Private
.
In lessons parts of our course, we've used get methods to read Private fields.
We would name them something like GetAge() and so on. We'll create get methods
to be able to read certain fields and make these fields Private to prevent them
from being modified from the outside. The class would now look something like
this (I omitted the constructor and ToString()):
Public Class Student Private fullName As String Private male As Boolean Private age As Integer Private fullAged As Boolean ... Public Function GetFullName() As String Return fullName End Function Public Function GetFullAged() As Boolean Return fullAged End Function Public Function GetAge() As Integer Return age End Function Public Function IsMale() As Boolean Return male End Function Public Sub SetAge(value As Integer) age = value ' updating whether student is of age fullAged = True If age < 18 Then fullAged = False End If End Sub End Class
The methods just returning a value are very simple. In the method setting the
age is some more logic, since we have to reconsider the fullAged
field. We made sure we can't set variables in any other way than what we want.
We now control all the field changes and can work with them as needed. Our
design must prevent all unwanted changes of the internal state that would cause
an object to malfunction.
Methods for returning values are called getters and methods for setting values are called setters. We could potentially add an EditStudent() method to edit the other fields sort of like the constructor. Essentially, the student's name, age, and other fields would be updated using this method. We could also validate the values being set there since we would be able to handle all attempts to change certain values in one place. Implementing getters and setters manually is without a doubt hard work. Couldn't someone just do it for us? Yep, VB.NET is able to generate them for us! In that case, we're no longer talking about fields, rather properties.
The property syntax is very similar to the field syntax:
Public Property FullName As String
At first, it may seem as if we had declared a field. The property name is capitalized because it is actually a method (2 methods to be precise). We use the Property keyword to declare properties. In the example above, both the setter and the getter would be generated, and the property would be accessible both for reading and writing:
Console.WriteLine(object.FullName) ' reading object.FullName = "John Black" ' writing
From the outside, the only significant difference against an attribute is that the first letter is uppercase. VB.NET internally generates a Private field and two public methods that are called automatically depending on the context (whether we read or write the value).
If you want to prevent all alteration of your variable from outside the class, you can make the property ReadOnly
Public ReadOnly Property FullName As String = "John Smith"
Unfortunately, this applies for us as well so we won't be able to assign anything to this property, ever from the inside of the class. Therefore, we won't uses that very often.
If we want to implement custom getter and setter, the syntax is as following:
Private _FullName As String Public Property FullName As String Get Return _FullName End Get Set(ByVal value As String) _FullName = value End Set End Property
When declaring custom getters and setters, we have to store the value somewhere. We store it into a private attribute which is usually prefixed with an underscore and has the same name as the property has.
If we didn't generate the setter part of the property, there would be no way to change the property from the inside nor the outside. If you want to prevent all alteration of your variable from outside the class, you would just make its setter private:
Private _FullName As String Public Property FullName As String Get Return _FullName End Get Private Set(ByVal value As String) _FullName = value End Set End Property
We'll use this very often and most of the properties in our classes will look like this from now on.
Let's take our example of the full-aged problem which must be re-evaluated when the student's age changes:
Private _Age As Integer Property Age As Integer Get Return _Age End Get Set(ByVal value As Integer) _Age = value ' updating whether student is of age fullAged = True If _Age < 18 Then fullAged = False End If End Set End Property
First and foremost, we'll have to create a Private _Age field, the value will be stored there. We'll work with this field in the getter and the setter. If you were to use "Age", without the underscore, in the getter or setter, the program would get stuck in an infinite loop! Why? Well, take another look at the code above (hint: the method we are currently in has already been declared as that).
You cannot implement a custom getter and let the setter be generated automatically. They have to either both be generated automatically or both be implemented manually. To access the value being set in the setter, we use the value parameter. All properties had to be implemented like this until VB.NET added auto-implemented properties in version 4.0. As a matter of fact, we don't need any logic at all in the most of the properties. We'll treat the Age as we would treat a field from now (remember it is case sensitive). Re-assignment of the "Age" triggers the internal logic to update the fullAged field:
object.Age = 15 ' the fullAged field will update immediately as well
Likewise, we could implement a custom getter and log something.
Let's update our Student class so it'll use properties:
Public Class Student Private _FullName As String Property FullName As String Get Return _FullName End Get Private Set(ByVal value As String) _FullName = value End Set End Property Private _Male As Boolean Property Male As Boolean Get Return _Male End Get Private Set(ByVal value As Boolean) _Male = value End Set End Property Private _FullAged As Boolean Property FullAged As Boolean Get Return _FullAged End Get Private Set(ByVal value As Boolean) _FullAged = value End Set End Property Private _Age As Integer Property Age As Integer Get Return _Age End Get Set(ByVal value As Integer) _Age = value ' updating whether student is of age FullAged = True If age < 18 Then FullAged = False End If End Set End Property Public Sub New(fullName As String, gender As Boolean, age As Integer) EditStudent(fullName, gender, age) End Sub Public Sub EditStudent(fullName As String, gender As Boolean, age As Integer) Me.FullName = fullName Me.Male = gender Me.Age = age End Sub Public Overrides Function ToString() As String Dim iAmFullAged As String = "I am" If Not FullAged Then iAmFullAged = "I am not" End If Dim gender As String = "male" If Not Male Then gender = "female" End If Return String.Format("I am {0}, {1}. I am {2} years old and {3} of age.", fullName, gender, Age, iAmFullAged) End Function End Class
From now on, we will always use properties rather than fields since they allow us to encapsulate objects perfectly. In the .NET framework, all public class members are properties. For example, the Length property of a String. There's a general design guideline that we follow that states: values that are allowed to exit a class must be properties, and values that are internal and "non-editable", must be private fields. Overall, we don't use public fields. The whole class and demo app are, of course, available for download below the article. We can now remove the fullAged checking from the constructor since we now set the age using the Age property, and the FullAged property is updated automatically. Let's try the code which caused issues earlier:
{VBNET_OOP}
Public Class Student
Private _FullName As String
Property FullName As String
Get
Return _FullName
End Get
Private Set(ByVal value As String)
_FullName = value
End Set
End Property
Private _Male As Boolean
Property Male As Boolean
Get
Return _Male
End Get
Private Set(ByVal value As Boolean)
_Male = value
End Set
End Property
Private _FullAged As Boolean
Property FullAged As Boolean
Get
Return _FullAged
End Get
Private Set(ByVal value As Boolean)
_FullAged = value
End Set
End Property
Private _Age As Integer
Property Age As Integer
Get
Return _Age
End Get
Set(ByVal value As Integer)
_Age = value
' updating whether student is of age
FullAged = True
If age < 18 Then
FullAged = False
End If
End Set
End Property
Public Sub New(fullName As String, gender As Boolean, age As Integer)
EditStudent(fullName, gender, age)
End Sub
Public Sub EditStudent(fullName As String, gender As Boolean, age As Integer)
Me.FullName = fullName
Me.Male = gender
Me.Age = age
End Sub
Public Overrides Function ToString() As String
Dim iAmFullAged As String = "I am"
If Not FullAged Then
iAmFullAged = "I am not"
End If
Dim gender As String = "male"
If Not Male Then
gender = "female"
End If
Return String.Format("I am {0}, {1}. I am {2} years old and {3} of age.", FullName, gender, Age, iAmFullAged)
End Function
End Class
{VBNET_MAIN_BLOCK}
Dim s As Student = New Student("Peter Brown", True, 20)
s.Age = 15
's.Male = False ' This line now causes an error and has to be removed
Console.WriteLine(s)
{/VBNET_MAIN_BLOCK}
{/VBNET_OOP}
The output:
Console application
I am Peter Brown, male. I am 15 years old and I am not of age.
If we set the entire property as private, setters and getters will not be able to be marked as public.
In the next lesson, Date and time in VB.NET, we'll learn, how to work with date and time in .NET.