 .NET 1.1C# Class Properties
The third article in the C# Object-Oriented Programming tutorial expands upon the previous creation of simple classes by introducing properties. Properties of a class allow instantiated objects to have state with each object controlling its own data.
Adding State
In the previous article of this series, I explained the basic syntax and process for creating a class that has methods as a part of its public interface. This allows the generation of simple classes with behaviour but without state. The state of an object describes its individual data or unique configuration. In the example of a car object, methods allow us to accelerate or decelerate but the state, described in properties, allows us to determine the actual speed and to describe one car object as red and another as blue. This information creates a real differentiation between the two objects.
In this article, we will create a new class to represent a rectangle shape. This class will define four properties: the rectangle's height, width, area and perimeter. To begin, create a new console application and add a new class named "Rectangle".
Adding A Property
Creating a Class-Level Variable
In many cases, the information that is made public via a property is held directly within the object as a variable. This variable has a scope that makes it visible to the entire class. This is not always the case however, as the information may be held in a database or other external source or may be calculated rather than stored. In the example class in this article, two of the property values will be held in variables and two will be calculated.
To define a class-scoped variable, the declaration is made within the class' code block but outside of any methods or properties. Although not required, it is useful to precede the variable declaration with the private keyword to make the code clear and easy to read. Using this syntax, we can add two integer variables to the Rectangle class to hold the height and width.
class Rectangle
{
private int _width;
private int _height;
}
NB: The use of lower camel case and an underscore (_) prefix is one naming standard for class-level variables. It is a useful, though not essential, convention.
Exposing a Property
Adding a property to a class is similar to adding a variable. In fact, it is possible to create a rudimentary property by adding a variable at the class scope and simply declaring it as public instead of private. This actually creates a public field. However, this is inadvisable as it means that the class will have no control over the values that the properties are set to and some standard .NET functionality is lost. To avoid these problems the property is declared with a code block as follows:
Once declared, functionality must be added to the property. This is achieved with the use of get and set accessors. These create two code blocks that control how the property's value is retrieved or calculated when requested (get accessor) and how the property's value is validated, processed and stored when assigned a value (set accessor). The two accessors are added to the property using the get and set keywords, each with its own code block:
public int Width
{
get
{
}
set
{
}
}
To complete the Width method, we now need to add the code that processes the getting and setting of the properties. This is relatively simple for the get accessor. When the property value is requested, we will simply return the value from the class-level variable. To do this, we can use the return keyword with the same syntax as for any functional method.
public int Width
{
get
{
return _width;
}
set
{
}
}
When using the set accessor, the value that the external object is assigning to the property is passed as a variable named "value". This can be thought of as similar to a method parameter even though its name is hidden.
For the Width property, we will validate the provided value before storing it. If the value is negative or is greater than one hundred, an exception will be thrown and the property will remain unchanged. This is possible because of the correct usage of get and set accessors. If a simple public variable had been created as described earlier, this level of control would not be available.
public int Width
{
get
{
return _width;
}
set
{
if (value < 0 || value > 100)
throw new OverflowException();
_width = value;
}
}
Before we try out the new property, let's add a second property for the height. The same validation rules apply:
public int Height
{
get
{
return _height;
}
set
{
if (value < 0 || value > 100)
throw new OverflowException();
_height = value;
}
}
Using Properties
Properties of instantiated objects are accessed using the object name followed by the member access operator (.) and the property name. The property can be read from and written to using similar syntax as for a standard variable. To demonstrate this, consider the following example code, added to the Main method of the program. It creates two objects based upon the Rectangle class and assigns and reads their properties individually. When trying to assign an invalid height to a rectangle, an exception is thrown by the validation code.
static void Main(string[] args)
{
Rectangle rect = new Rectangle();
rect.Width = 50;
rect.Height = 25;
Rectangle square = new Rectangle();
square.Height = square.Width = 40;
Console.WriteLine(rect.Height); // Outputs "25"
Console.WriteLine(square.Width); // Outputs "40"
rect.Height = 25; // Throws the validation exception.
}
NB: The sample code demonstrates both encapsulation and state. The state of the two rectangles is held internally and independently and the implementation details of the properties are hidden from the program. If, in the future, we need to move the data from the private variables into an XML file or database, the Main method will not need to be changed.
Read-Only and Write-Only Properties
The Width and Height properties are examples of read-write properties because the information held within them can be both read and written. Sometimes you will want read-only properties or even write-only properties. These are easy to create by simply not specifying the accessors that are not required.
We can now add read-only properties for the area and perimeter of the rectangle with omitted set accessors. These properties are calculated from the existing class-level variables so require no direct internal storage. The code is as follows:
public int Area
{
get
{
return _height * _width;
}
}
public int Perimeter
{
get
{
return 2 * (_height + _width);
}
}
The new read-only properties can be read using the same syntax as the read-write properties. Test this by modifying the program's Main method:
static void Main(string[] args)
{
Rectangle rect = new Rectangle();
rect.Width = 8;
rect.Height = 12;
Console.WriteLine(rect.Area); // Outputs "96"
Console.WriteLine(rect.Perimeter); // Outputs "40"
}
Using the Property Internally
In the Rectangle example, the area and perimeter properties are calculated using the class-level variables that hold the actual values. This is not always possible, or indeed advisable, particularly if a property involves a calculation or validation process. Rather than duplicating the code for the calculation or validation, the property can be accessed by name. We could, therefore, adjust the Area property as follows:
public int Area
{
get
{
return Height * Width;
}
}
With this updated version of the Area property, the source of the height and width values has been disassociated from the internal variables. If the Height and Width properties are changed in the future to retrieve their values from a database, no changes to the Area property will be required.
The "this" Keyword
A final keyword to consider is "this". The "this" keyword can be used within a class' code to refer to the current instantiated object. For properties this is rarely required unless poor naming of variables has led to a property name being hidden. In the following example, the method compares the area of the current object to an area value passed as a parameter. To access the property name, the "this" keyword is used because the method's parameter has been badly named and thus hides the property:
public bool AreaTheSame(int area)
{
if (this.Area == area)
return true;
else
return false;
}
NB: It would be more appropriate to rename the method's parameter in the above situation.
The "this" keyword does have another use. If a method is being called and the current object is to be passed to that method, setting a parameter to "this" achieves the desired result. For example:
floorShape.Set(this);
|